Otelic Logo

Resolve your LOGS & TRACES issues within minutes

NativeOpenTelemetry support
gRPC support without anything from your side
Just add Otelic in yourOpenTelemetry collector
Otelic.com is built on top of open-source market's best tooling for logs & traces - Open Telemetry. What you need to do is follow Open Telemetryguidesand point your logs & traces exporters to Otelic endpoints with header to authorize requests. It works with Javascript, C++, C#, .NET, Erlang, Go, Java, PHP, Python, Ruby, Rust, Swift.
Logs exporter endpoint:
https://ingest.otelic.com/otel/v1/logs
Traces exporter endpoint:
https://ingest.otelic.com/otel/v1/traces
Set API key with header:
x-api-key get for free in workspace settings
Bellow you will find full sample for NodeJS app. It's all standard OpenTelemetry code. You can do same thing with any other language supported by OpenTelemetry too: Javascript, C++, C#, .NET, Erlang, Go, Java, PHP, Python, Ruby, Rust, Swift.
Here is NodeJS sample
  1. Create workspace atapp.otelic.com
  2. Create a file. E.g.otelic.ts 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

For support, contact us at support@otelic.com

© 2024 Otelic.com - All Rights Reserved.