Sending OTLP Logs from JavaScript

TL;DR: right now OTel Logs are a little harder to use than span events, but otherwise they are superior.

Today I wanted to send OTLP Logs and see them correlated with my traces. First problem: logs aren’t available in the OTel JavaScript SDK. They’re still in development. (It is the end of 2023.)

There is an implementation under experimental/packages. To use it, ya gotta register a provider. (That’s how we set up traces back in the day–before the nice SDK interface–and sometimes still do in the browser examples.) There’s a pile of essential boilerplate to set the service.name field.

import * as logsAPI from "@opentelemetry/api-logs";
import {
LoggerProvider,
BatchLogRecordProcessor,
} from "@opentelemetry/sdk-logs";
import { Resource } from "@opentelemetry/resources";
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
import { diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api";

console.log("Initializing OpenTelemetry Logging...");

// this is for troubleshooting. Otherwise you won't see connection errors
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO);

// gotta make a provider
const loggerProvider = new LoggerProvider({
// gotta give it a resource
resource: new Resource({
// in order to give it a service name.
[SemanticResourceAttributes.SERVICE_NAME]: process.env.OTEL_SERVICE_NAME,
}),
});

// tell it where to put them
loggerProvider.addLogRecordProcessor(
new BatchLogRecordProcessor(
// by default, this will send to a local OpenTelemetry Collector
new OTLPLogExporter()
)
);

// logger provider, activate!
logsAPI.logs.setGlobalLoggerProvider(loggerProvider);

That whole initialization is in addition to whatever you’re doing for tracing. The two are independent as far as I can tell.

The intention of OpenTelemetry logs is to provide a backing for existing logging frameworks. You’re not really supposed to use them directly. But I don’t have another logging framework in my app, so I choose to use them directly.

Creating a log looks like this:

import * as otelLogs from "@opentelemetry/api-logs";

const logger = otelLogs.logs.getLogger("my custom logger");

// then later, in your functions...
logger.emit({
body: "Wow, look at this exciting thing that happened",
severityNumber: otelLogs.SeverityNumber.DEBUG,
severityText: "JESS WAS HERE",
attributes: {
excitementLevel: "low",
customerID: "asdkfj79234",
      name: "JESS WAS HERE",
},
});

The log will automatically include the trace and span ID from the OpenTelemetry active context.

These logs show up in Honeycomb just like span events, as a circle in the span’s row.

Adding a span event is easier, because you don’t need to create a logger. The code for that is:

context.getActiveSpan().addEvent("JESS WAS HERE");

Two reasons to use OTel logs instead of span events:
1. The log gets sent immediately. The span event hangs out with the span until it ends. If the span does not end (process crashes, browser window closes), the span event is dropped with it.
2. The log can be sent after the span has ended. Span events can’t be created on a span that’s already been sent.

There are a few tricky bits at the moment for Honeycomb in particular. I had to get the service name right, matching the one for traces. (That will stay true, but it’ll be easier once logs are part of the standard SDK.) I had to send “JESS WAS HERE” in both a name attribute and a severityText field to get the span events to show it both on the timeline and in the sidebar. They’ll fix that. And finally, there’s some interaction bug between the Node SDK and Honeycomb’s ingest that mangled span IDs–when I sent it through a local collector it worked fine. They’ll fix that too, but I’ll record it here in case you’re trying this in early 2024.

Overall, I’m happy with OTel logs as better span events. They send independently, and they’re queryable independent of the spans they associate with. (Span events are too, but nobody adds enough attributes for that to be useful.)

Discover more from Jessitron

Subscribe now to keep reading and get access to the full archive.

Continue reading