Send Logs Using the OpenTelemetry SDK
Send logs from your application directly to Middleware with the OpenTelemetry SDK. The SDK attaches to your existing logging library (for example, Python logging, Logback, or Log4j), converts each log record to the OpenTelemetry log format, and exports it to Middleware over OTLP. No agent is required.
When a log is written inside an active span, the SDK adds its trace_id and span_id, so you can jump from a log to its trace in Middleware.
1Application (logging library + OTel SDK) ──(OTLP)──► MiddlewareIf the Middleware Agent runs on the same host, you can point the SDK at the agent instead (http://localhost:9320 for OTLP/HTTP or http://localhost:9319 for OTLP/gRPC) and skip the authorization header.
Prerequisites#
- Your Middleware UID (
https://<uid>.middleware.io) and API key (MW_API_KEY). - Network access from your application to
https://<uid>.middleware.io:443.
Configure the exporter#
All OpenTelemetry SDKs read the same environment variables. Set them before you start your application:
1export OTEL_SERVICE_NAME="my-service"
2export OTEL_EXPORTER_OTLP_ENDPOINT="https://<MW_UID>.middleware.io:443"
3export OTEL_EXPORTER_OTLP_HEADERS="authorization=<MW_API_KEY>"
4export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
5export OTEL_LOGS_EXPORTER="otlp"
6
7# optional: send only logs
8export OTEL_TRACES_EXPORTER="none"
9export OTEL_METRICS_EXPORTER="none"Replace:
<uid>with your Middleware project UID.<MW_API_KEY>with your Middleware API key.my-servicewith the name of your service (for example,payments-api).
With http/protobuf, the SDK sends logs to https://<uid>.middleware.io:443/v1/logs.
Instrument your application#
Option A: Auto-instrumentation (recommended)#
Auto-instrumentation sends records from the standard logging module to Middleware without code changes.
- Install the packages:
1pip install opentelemetry-distro opentelemetry-exporter-otlp 2opentelemetry-bootstrap -a install - Set the environment variables and enable logging instrumentation:
1export OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true - Start your application with
opentelemetry-instrument:1opentelemetry-instrument python main.py
Option B: Manual SDK setup#
- Install the packages:
1pip install opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp-proto-http opentelemetry-instrumentation-logging - Add a
LoggingHandlerto the root logger at application startup. The exporter reads the endpoint and headers from the environment variables above.1import logging 2 3from opentelemetry._logs import set_logger_provider 4from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter 5from opentelemetry.instrumentation.logging.handler import LoggingHandler 6from opentelemetry.sdk._logs import LoggerProvider 7from opentelemetry.sdk._logs.export import BatchLogRecordProcessor 8from opentelemetry.sdk.resources import Resource 9 10logger_provider = LoggerProvider(resource=Resource.create()) 11set_logger_provider(logger_provider) 12logger_provider.add_log_record_processor(BatchLogRecordProcessor(OTLPLogExporter())) 13 14handler = LoggingHandler(level=logging.NOTSET, logger_provider=logger_provider) 15logging.getLogger().addHandler(handler) 16logging.getLogger().setLevel(logging.INFO) 17 18logging.info("Hello from Python") - Call
logger_provider.shutdown()before the process exits so that buffered logs are sent.
- Install the packages:
1npm install @opentelemetry/api-logs @opentelemetry/sdk-logs \ 2 @opentelemetry/exporter-logs-otlp-http @opentelemetry/resources \ 3 @opentelemetry/semantic-conventions - Create
logger.js. The exporter reads the endpoint and headers from the environment variables above.1const { LoggerProvider, BatchLogRecordProcessor } = require("@opentelemetry/sdk-logs"); 2const { OTLPLogExporter } = require("@opentelemetry/exporter-logs-otlp-http"); 3const { resourceFromAttributes } = require("@opentelemetry/resources"); 4const { ATTR_SERVICE_NAME } = require("@opentelemetry/semantic-conventions"); 5const { SeverityNumber } = require("@opentelemetry/api-logs"); 6 7const loggerProvider = new LoggerProvider({ 8 resource: resourceFromAttributes({ 9 [ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || "my-service", 10 }), 11 processors: [new BatchLogRecordProcessor({ exporter: new OTLPLogExporter() })], 12}); 13 14const logger = loggerProvider.getLogger("default"); 15 16process.on("SIGTERM", () => loggerProvider.shutdown()); 17 18module.exports = { logger, SeverityNumber }; - Emit logs from your application:
1const { logger, SeverityNumber } = require("./logger"); 2 3logger.emit({ 4 severityNumber: SeverityNumber.INFO, 5 severityText: "INFO", 6 body: "Hello from Node.js", 7 attributes: { "user.id": "123" }, 8});
Option A: Java agent (recommended)#
The OpenTelemetry Java agent captures logs from Logback, Log4j 2, and java.util.logging without code changes.
- Download the agent:
1wget https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar - Set the environment variables and start your application with the agent:
1java -javaagent:./opentelemetry-javaagent.jar -jar your-app.jar
Option B: Manual SDK setup (Logback)#
- Add the dependencies (Maven):
1<dependencyManagement> 2 <dependencies> 3 <dependency> 4 <groupId>io.opentelemetry</groupId> 5 <artifactId>opentelemetry-bom</artifactId> 6 <version>1.57.0</version> 7 <type>pom</type> 8 <scope>import</scope> 9 </dependency> 10 <dependency> 11 <groupId>io.opentelemetry.instrumentation</groupId> 12 <artifactId>opentelemetry-instrumentation-bom-alpha</artifactId> 13 <version>2.23.0-alpha</version> 14 <type>pom</type> 15 <scope>import</scope> 16 </dependency> 17 </dependencies> 18</dependencyManagement> 19 20<dependencies> 21 <dependency> 22 <groupId>io.opentelemetry</groupId> 23 <artifactId>opentelemetry-sdk</artifactId> 24 </dependency> 25 <dependency> 26 <groupId>io.opentelemetry</groupId> 27 <artifactId>opentelemetry-sdk-extension-autoconfigure</artifactId> 28 </dependency> 29 <dependency> 30 <groupId>io.opentelemetry</groupId> 31 <artifactId>opentelemetry-exporter-otlp</artifactId> 32 </dependency> 33 <dependency> 34 <groupId>io.opentelemetry.instrumentation</groupId> 35 <artifactId>opentelemetry-logback-appender-1.0</artifactId> 36 </dependency> 37</dependencies> - Add the OpenTelemetry appender to
src/main/resources/logback.xml:1<configuration> 2 <appender name="OTEL" class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender"> 3 <captureExperimentalAttributes>true</captureExperimentalAttributes> 4 <captureKeyValuePairAttributes>true</captureKeyValuePairAttributes> 5 </appender> 6 <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> 7 <encoder> 8 <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> 9 </encoder> 10 </appender> 11 <root level="INFO"> 12 <appender-ref ref="OTEL" /> 13 <appender-ref ref="CONSOLE" /> 14 </root> 15</configuration> - Initialize the SDK and install the appender at startup. The SDK reads the endpoint and headers from the environment variables above.
1import io.opentelemetry.sdk.OpenTelemetrySdk; 2import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; 3import io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender; 4import org.slf4j.Logger; 5import org.slf4j.LoggerFactory; 6 7public class Application { 8 private static final Logger logger = LoggerFactory.getLogger(Application.class); 9 10 public static void main(String[] args) { 11 OpenTelemetrySdk sdk = AutoConfiguredOpenTelemetrySdk.initialize().getOpenTelemetrySdk(); 12 OpenTelemetryAppender.install(sdk); 13 14 logger.info("Hello from Java"); 15 } 16}
OpenTelemetry provides logs SDKs for other languages as well, at different maturity levels. SDKs that follow the OpenTelemetry specification read the environment variables above, so the setup is:
- Install the OpenTelemetry logs SDK and OTLP log exporter for your language.
- Create a
LoggerProviderwith a batch processor and the OTLP log exporter. - Connect your logging library through its OpenTelemetry bridge or appender.
See the OpenTelemetry language documentation for language-specific packages and the status of each logs SDK.
Verify#
Run your application and write a few log lines. Logs appear in the Logs section of your Middleware account, with service.name set to the value of OTEL_SERVICE_NAME.
Environment variable reference#
| Variable | Description | Example |
|---|---|---|
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT | Logs-only endpoint. Overrides OTEL_EXPORTER_OTLP_ENDPOINT for logs. | https://<uid>.middleware.io:443/v1/logs |
OTEL_EXPORTER_OTLP_LOGS_HEADERS | Logs-only headers. | authorization=<MW_API_KEY> |
OTEL_EXPORTER_OTLP_LOGS_PROTOCOL | Logs-only protocol. | http/protobuf, grpc |
OTEL_RESOURCE_ATTRIBUTES | Extra resource attributes added to every log. | deployment.environment=production |
OTEL_BLRP_SCHEDULE_DELAY | Delay between batch exports, in milliseconds. | 5000 |
OTEL_BLRP_MAX_QUEUE_SIZE | Maximum number of queued log records. | 2048 |
OTEL_BLRP_MAX_EXPORT_BATCH_SIZE | Maximum number of log records per export. | 512 |
With grpc, use the root endpoint (https://<uid>.middleware.io:443) without /v1/logs. The /v1/logs path is only for http/protobuf.
Troubleshooting#
- No logs in Middleware: print the variables (
env | grep OTEL_) in the same shell or container that runs the application. 400 Bad Requesterrors: check theauthorizationvalue inOTEL_EXPORTER_OTLP_HEADERS. Middleware returns400for a missing or invalid API key.- Logs from short-lived processes are missing: shut down the
LoggerProvider(or callforceFlush) before the process exits. - Logs are not linked to traces: make sure tracing is also set up.
trace_idandspan_idare only added when a log is written inside an active span.
Need assistance or want to learn more about Middleware? Get in touch with us via our Contact Us or join our Slack channel.