Skip to content

Instantly share code, notes, and snippets.

@hazelweakly
Last active May 11, 2022 16:36
Show Gist options
  • Save hazelweakly/a23cd6a16101208969806fbd9f0f2835 to your computer and use it in GitHub Desktop.
Save hazelweakly/a23cd6a16101208969806fbd9f0f2835 to your computer and use it in GitHub Desktop.
opentelemetry-js utility functions
import {
Attributes,
Span,
SpanContext,
SpanOptions,
SpanStatusCode,
trace,
} from "@opentelemetry/api";
export const serviceName = process.env.SERVICE_NAME ?? "o11y-demo-app-front-end";
export const tracer = () => trace.getTracer(serviceName);
export function withSpan<T>(span: Span, fn: (s: Span) => T): T {
const onCatch = (e: unknown) => {
const error =
e instanceof Error ? e : new Error(typeof e === "string" ? e : undefined);
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
span.recordException(error);
throw error;
};
const onFinally = () => span.end();
let result;
try {
result = fn(span) as T | Promise<T>;
return result instanceof Promise
? (result
.then((v) => v)
.catch(onCatch)
.finally(onFinally) as unknown as T)
: result;
} catch (e: unknown) {
if (!(result instanceof Promise)) return onCatch(e);
throw e;
} finally {
if (!(result instanceof Promise)) onFinally();
}
}
export const withNewSpan = <T>(
name: string | (SpanOptions & { name: string }),
fn: (s: Span) => T
) => {
const { name: n, ...args } = typeof name === "string" ? { name } : name;
return tracer().startActiveSpan(n, args, (span) => withSpan(span, fn));
};
const toCtx = (l: Span | SpanContext) => ({
context: "spanContext" in l ? l.spanContext() : l,
});
export const newLinkedSpan = (
name: string | (SpanOptions & { name: string }),
toLink: Span | Span[] | SpanContext | SpanContext[]
) => {
const { name: n, ...args } = typeof name === "string" ? { name } : name;
const links = Array.isArray(toLink) ? toLink.map(toCtx) : [toCtx(toLink)];
return tracer().startSpan(n, {
...args,
links: [...(args.links ?? []), ...links],
});
};
export const objToAttrs = (
prefix: string,
o: Record<string, any>
): Attributes =>
Object.fromEntries(
Object.entries(o)
.filter(([_, v]) => v !== undefined && v !== null)
.map(([k, v]) => [
`${prefix}.${k}`,
typeof v === "object" ? JSON.stringify(v) : v,
])
);
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
import {
ConsoleSpanExporter,
BatchSpanProcessor,
SimpleSpanProcessor,
} from "@opentelemetry/sdk-trace-base";
import { registerInstrumentations } from "@opentelemetry/instrumentation";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { Resource } from "@opentelemetry/resources";
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
import { serviceName } from "./trace-utils";
if (typeof window === "undefined") {
const exporter = new OTLPTraceExporter({
url: "http://localhost:4318/v1/traces",
});
const resource = new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: serviceName,
});
const provider = new NodeTracerProvider({ resource });
provider.addSpanProcessor(new BatchSpanProcessor(exporter));
if (process.env.NODE_ENV !== "production") {
provider.addSpanProcessor(
new SimpleSpanProcessor(new ConsoleSpanExporter())
);
}
provider.register();
registerInstrumentations({
instrumentations: [
getNodeAutoInstrumentations({
"@opentelemetry/instrumentation-http": {
ignoreIncomingPaths: [
"/ping",
...(process.env.NODE_ENV !== "production"
? [/^\/_next\/static.*/]
: []),
],
},
}),
],
});
}
import { WebTracerProvider } from "@opentelemetry/sdk-trace-web";
import { getWebAutoInstrumentations } from "@opentelemetry/auto-instrumentations-web";
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
import { registerInstrumentations } from "@opentelemetry/instrumentation";
import { ZoneContextManager } from "@opentelemetry/context-zone";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { Resource } from "@opentelemetry/resources";
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
import { serviceName } from "./trace-utils";
if (typeof window !== "undefined") {
const exporter = new OTLPTraceExporter({
url: "http://localhost:4318/v1/traces",
});
const resource = new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: serviceName,
});
const provider = new WebTracerProvider({ resource });
provider.addSpanProcessor(new BatchSpanProcessor(exporter));
provider.register({
contextManager: new ZoneContextManager(),
});
// Assume this is fancy logic
const isHighVolumePage = false;
const backendUrls = /^\/api.+/g;
registerInstrumentations({
instrumentations: [
getWebAutoInstrumentations({
"@opentelemetry/instrumentation-document-load": {
enabled: !isHighVolumePage,
},
"@opentelemetry/instrumentation-xml-http-request": {
propagateTraceHeaderCorsUrls: [backendUrls],
},
"@opentelemetry/instrumentation-fetch": {
propagateTraceHeaderCorsUrls: [backendUrls],
},
}),
],
});
}
@hazelweakly
Copy link
Author

Involved example of usage:

export default async function handler(req, res) {
  return withNewSpan(
    {
      name: `/api/v1/form/{event}`,
      attributes: makeSemanticHandlerAttributes(req),
      kind: SpanKind.SERVER,
    },
    async (span) => {
      const valid = validate(req);
      const status = valid ? 200 : 400;
      span.setAttributes(objToAttrs("app.api.parameters", req.body));

      // This span is linked manually because the lifetime of the server response
      // is not tied to this callback.
      const linkedSpan = newLinkedSpan(
        {
          name: `response /api/v1/form/{event}`,
          attributes: {
            ...(snip a bunch of SemanticAttributes)
          },
          kind: SpanKind.SERVER,
        },
        span
      );
      linkedSpan.addEvent("Sending response");

      res.status(status).json(prepareResponse(req, res));
      span.setStatus(valid ? okStatus() : errStatus());

      res.once("finish", () => {
        linkedSpan.addEvent("Finished sending response");
        linkedSpan.setStatus({ code: SpanStatusCode.OK });
        linkedSpan.end();
      });
    }
  );
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment