Otelic Logo

Open Telemetry Logs & Traces for NodeJS app

  1. Create workspace atapp.otelic.com
  2. Create a file. E.g.otelic.js and copy code (see below)
  3. Check lines 28, 29, 30, and 31 to add your free Api Key (from workspace settings) and app/service names.
  4. Install dependencies & import this new file as first thing in your code
OpenTelemetry dependencies:
npm install @opentelemetry/api @opentelemetry/api-logs @opentelemetry/sdk-logs @opentelemetry/resources @opentelemetry/semantic-conventions @opentelemetry/exporter-logs-otlp-http @opentelemetry/exporter-trace-otlp-http @opentelemetry/sdk-trace-node @opentelemetry/sdk-trace-base @opentelemetry/instrumentation @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node
1import os from "os";
2import path from "path";
3import util from "util";
4import { context, trace } from "@opentelemetry/api";
5import { SeverityNumber } from "@opentelemetry/api-logs";
6import {
7  BatchLogRecordProcessor,
8  LoggerProvider,
9} from "@opentelemetry/sdk-logs";
10import { Resource } from "@opentelemetry/resources";
11import {
12  ATTR_SERVICE_NAME,
13  ATTR_SERVICE_VERSION,
14} from "@opentelemetry/semantic-conventions";
15import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
16import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
17import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
18import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
19import { registerInstrumentations } from "@opentelemetry/instrumentation";
20import { NodeSDK } from "@opentelemetry/sdk-node";
21import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
22
23//
24//
25//
26//
27// CONFIG --- start ---
28const SERVICE_NAME = process.env.OTELIC_SERVICE_NAME || "My Service";
29const SERVICE_VERSION = process.env.OTELIC_SERVICE_VERSION || "v1.0";
30const APP_NAME = process.env.OTELIC_APP_NAME || "My App";
31const API_KEY = process.env.OTELIC_API_KEY || "GET_FROM_WORKSPACE_SETTINGS";
32
33// Default:  ["log", "info", "warn", "error", "debug"]
34const CONSOLE_METHODS_TO_INSTRUMENT = ["log", "info", "warn", "error", "debug"];
35
36// CONFIG --- end -----
37//
38//
39//
40//
41//
42//
43//
44//
45
46const traceExporter = new OTLPTraceExporter({
47  url: "https://ingest.otelic.com/otel/v1/traces",
48  headers: { "x-api-key": API_KEY },
49  concurrencyLimit: 3,
50});
51
52const logExporter = new OTLPLogExporter({
53  url: "https://ingest.otelic.com/otel/v1/logs",
54  headers: { "x-api-key": API_KEY },
55  concurrencyLimit: 3,
56});
57
58const resource = new Resource({
59  [ATTR_SERVICE_NAME]: SERVICE_NAME,
60  [ATTR_SERVICE_VERSION]: SERVICE_VERSION,
61  "app.name": APP_NAME,
62  "deployment.environment.name": process.env.NODE_ENV || "",
63  "host.name": os.hostname(),
64  "os.type": os.type(),
65  "os.version": os.version(),
66});
67
68const loggerProvider = new LoggerProvider({ resource });
69const processor = new BatchLogRecordProcessor(logExporter);
70loggerProvider.addLogRecordProcessor(processor);
71const loggerName = "nodejs";
72const loggerVersion = "1.0.0";
73const logger = loggerProvider.getLogger(loggerName, loggerVersion);
74
75const tracerProvider = new NodeTracerProvider({
76  resource,
77  spanProcessors: [new BatchSpanProcessor(traceExporter)],
78});
79tracerProvider.register();
80// const tracer = tracerProvider.getTracer(APP_NAME);
81
82registerInstrumentations({
83  instrumentations: [
84    getNodeAutoInstrumentations({
85      // load custom configuration for http instrumentation
86      "@opentelemetry/instrumentation-http": {
87        startIncomingSpanHook: (request) => {
88          const method = request.method;
89          const url = request.url;
90          return { operationName: `${method} ${url}` };
91        },
92      },
93      "@opentelemetry/instrumentation-mongodb": {
94        enhancedDatabaseReporting: true,
95      },
96      "@opentelemetry/instrumentation-mysql": {
97        enhancedDatabaseReporting: true,
98      },
99    }),
100  ],
101});
102
103const sdk = new NodeSDK({ resource, traceExporter });
104sdk.start();
105
106export function getLogContext() {
107  const activeSpan = trace.getSpan(context.active());
108  const traceId = activeSpan ? activeSpan.spanContext().traceId : "";
109  const spanId = activeSpan ? activeSpan.spanContext().spanId : "";
110
111  const fileName =
112    typeof require !== "undefined" && require.main?.filename
113      ? path.basename(require.main.filename)
114      : typeof import.meta !== "undefined" && import.meta.url
115        ? path.basename(new URL(import.meta.url).pathname)
116        : path.basename(process.argv[1]);
117
118  const filePath =
119    typeof require !== "undefined" && require.main?.filename
120      ? require.main.filename
121      : typeof import.meta !== "undefined" && import.meta.url
122        ? new URL(import.meta.url).pathname
123        : process.argv[1];
124
125  return {
126    "app.name": APP_NAME,
127    "service.name": SERVICE_NAME,
128    "env.node_env": process.env.NODE_ENV || "development",
129    "host.name": os.hostname(),
130    "os.type": os.type(),
131    "os.version": os.version(),
132    "file.name": fileName,
133    "file.path": filePath,
134    "log.source": "console",
135    "runtime.name": "nodejs",
136    "runtime.version": process.version,
137    "runtime.arch": process.arch,
138    "process.id": process.pid,
139    "process.name": process.title,
140    "process.command": process.argv.join(" "),
141    "process.uptime": process.uptime(),
142    traceId,
143    spanId,
144  };
145}
146
147process.on("uncaughtException", (error) => {
148  const message = `Uncaught Exception: ${error.message}\n${error.stack}`;
149
150  logger.emit({
151    severityNumber: SeverityNumber.FATAL,
152    severityText: "fatal",
153    body: message,
154    attributes: { "log.source": "uncaughtException" },
155  });
156
157  // Ensure logs are flushed before exiting
158  processor.shutdown().finally(() => {
159    console.error(message);
160    process.exit(1);
161  });
162});
163
164// Map console methods to OpenTelemetry severity levels
165const severityMap = {
166  log: { severityNumber: SeverityNumber.INFO, severityText: "info" },
167  info: { severityNumber: SeverityNumber.INFO, severityText: "info" },
168  warn: { severityNumber: SeverityNumber.WARN, severityText: "warn" },
169  error: { severityNumber: SeverityNumber.ERROR, severityText: "error" },
170  debug: { severityNumber: SeverityNumber.DEBUG, severityText: "debug" },
171};
172
173// Instrument console methods for logs
174const originalConsole = { ...console };
175
176CONSOLE_METHODS_TO_INSTRUMENT.map((method) => {
177  if (method in console) {
178    originalConsole[method] = console[method];
179
180    console[method] = (...args) => {
181      const { severityNumber, severityText } = severityMap[method];
182      let _parts = [];
183      args.forEach((arg) => {
184        if (arg instanceof Error) {
185          _parts.push(`${arg.message}\n${arg.stack}`);
186        } else if (typeof arg === "object") {
187          try {
188            _parts.push(JSON.stringify(arg));
189          } catch (error) {
190            _parts.push(util.inspect(arg, { depth: null, colors: false }));
191          }
192        } else {
193          _parts.push(arg.toString());
194        }
195      });
196      const message = _parts.join(" ");
197
198      // Emit the log to OpenTelemetry
199      logger.emit({
200        severityNumber,
201        severityText,
202        body: message,
203        attributes: {
204          ...getLogContext(),
205        },
206      });
207
208      // Call the original console method to keep standard logs functional
209      originalConsole[method](...args);
210    };
211  }
212});
213
214// Graceful shutdown to flush logs and traces before exit
215const gracefulShutdown = () => {
216  Promise.all([processor.shutdown(), tracerProvider.shutdown()]).finally(() => {
217    process.exit(0);
218  });
219};
220
221// Capture termination signals
222process.on("SIGINT", gracefulShutdown);
223process.on("SIGTERM", gracefulShutdown);
224

Resolve your LOGS & TRACES issues within minutes

Developer-friendlyquick setup
gRPC & HTTPS support out of the box
Just add Otelic to yourOpenTelemetry collector
Otelic.com is built on top of the open-source market's best tooling for logs & traces: Open Telemetry. Just follow the OpenTelemetry guides and point your logs & traces exporters to Otelic endpoints, using the x-api-key header to authorize requests. Otelic supports various languages and platforms, including JavaScript, C++, C#, .NET, Erlang, Go, Java, PHP, Python, Ruby, Rust, and Swift. It supports standalone deployments, serverless environments, Dockerized setups, Kubernetes, and more.

For support, contact us at support@otelic.com

© 2024 Otelic.com - All Rights Reserved.