---
title: "Send Logs Using the OpenTelemetry SDK"
description: "Send application logs directly to Middleware over OTLP using the OpenTelemetry SDK for Python, Node.js, Java, and other languages."
section: "OpenTelemetry"
breadcrumb: "Open Telemetry > Send Logs Using SDK"
canonical: "https://docs.middleware.io/get-started/agent/otel-logs"
---
# Send Logs Using the OpenTelemetry SDK

Send logs from your application directly to Middleware with the [OpenTelemetry SDK](https://opentelemetry.io/docs/languages/). 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.

```text
Application (logging library + OTel SDK) ──(OTLP)──► Middleware
```

> **Note:** If the [Middleware Agent](/get-started/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:

```bash
export OTEL_SERVICE_NAME="my-service"
export OTEL_EXPORTER_OTLP_ENDPOINT="https://<uid>.middleware.io:443"
export OTEL_EXPORTER_OTLP_HEADERS="authorization=<MW_API_KEY>"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
export OTEL_LOGS_EXPORTER="otlp"

# optional: send only logs
export OTEL_TRACES_EXPORTER="none"
export OTEL_METRICS_EXPORTER="none"
```

Replace:

- `<uid>` with your Middleware project UID.
- `<MW_API_KEY>` with your Middleware API key.
- `my-service` with 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

#### Python

### Option A: Auto-instrumentation (recommended)

Auto-instrumentation sends records from the standard `logging` module to Middleware without code changes.

1. Install the packages:

   ```bash
   pip install opentelemetry-distro opentelemetry-exporter-otlp
   opentelemetry-bootstrap -a install
   ```
2. Set the [environment variables](#configure-the-exporter) and enable logging instrumentation:

   ```bash
   export OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true
   ```
3. Start your application with `opentelemetry-instrument`:

   ```bash
   opentelemetry-instrument python main.py
   ```

### Option B: Manual SDK setup

1. Install the packages:

   ```bash
   pip install opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp-proto-http opentelemetry-instrumentation-logging
   ```
2. Add a `LoggingHandler` to the root logger at application startup. The exporter reads the endpoint and headers from the environment variables above.

   ```python
   import logging

   from opentelemetry._logs import set_logger_provider
   from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
   from opentelemetry.instrumentation.logging.handler import LoggingHandler
   from opentelemetry.sdk._logs import LoggerProvider
   from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
   from opentelemetry.sdk.resources import Resource

   logger_provider = LoggerProvider(resource=Resource.create())
   set_logger_provider(logger_provider)
   logger_provider.add_log_record_processor(BatchLogRecordProcessor(OTLPLogExporter()))

   handler = LoggingHandler(level=logging.NOTSET, logger_provider=logger_provider)
   logging.getLogger().addHandler(handler)
   logging.getLogger().setLevel(logging.INFO)

   logging.info("Hello from Python")
   ```
3. Call `logger_provider.shutdown()` before the process exits so that buffered logs are sent.

#### Node.js

1. Install the packages:

   ```bash
   npm install @opentelemetry/api-logs @opentelemetry/sdk-logs \
     @opentelemetry/exporter-logs-otlp-http @opentelemetry/resources \
     @opentelemetry/semantic-conventions
   ```
2. Create `logger.js`. The exporter reads the endpoint and headers from the environment variables above.

   ```javascript
   const { LoggerProvider, BatchLogRecordProcessor } = require("@opentelemetry/sdk-logs");
   const { OTLPLogExporter } = require("@opentelemetry/exporter-logs-otlp-http");
   const { resourceFromAttributes } = require("@opentelemetry/resources");
   const { ATTR_SERVICE_NAME } = require("@opentelemetry/semantic-conventions");
   const { SeverityNumber } = require("@opentelemetry/api-logs");

   const loggerProvider = new LoggerProvider({
     resource: resourceFromAttributes({
       [ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || "my-service",
     }),
     processors: [new BatchLogRecordProcessor({ exporter: new OTLPLogExporter() })],
   });

   const logger = loggerProvider.getLogger("default");

   process.on("SIGTERM", () => loggerProvider.shutdown());

   module.exports = { logger, SeverityNumber };
   ```
3. Emit logs from your application:

   ```javascript
   const { logger, SeverityNumber } = require("./logger");

   logger.emit({
     severityNumber: SeverityNumber.INFO,
     severityText: "INFO",
     body: "Hello from Node.js",
     attributes: { "user.id": "123" },
   });
   ```

#### Java

### Option A: Java agent (recommended)

The OpenTelemetry Java agent captures logs from Logback, Log4j 2, and `java.util.logging` without code changes.

1. Download the agent:

   ```bash
   wget https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar
   ```
2. Set the [environment variables](#configure-the-exporter) and start your application with the agent:

   ```bash
   java -javaagent:./opentelemetry-javaagent.jar -jar your-app.jar
   ```

### Option B: Manual SDK setup (Logback)

1. Add the dependencies (Maven):

   ```xml
   <dependencyManagement>
     <dependencies>
       <dependency>
         <groupId>io.opentelemetry</groupId>
         <artifactId>opentelemetry-bom</artifactId>
         <version>1.57.0</version>
         <type>pom</type>
         <scope>import</scope>
       </dependency>
       <dependency>
         <groupId>io.opentelemetry.instrumentation</groupId>
         <artifactId>opentelemetry-instrumentation-bom-alpha</artifactId>
         <version>2.23.0-alpha</version>
         <type>pom</type>
         <scope>import</scope>
       </dependency>
     </dependencies>
   </dependencyManagement>

   <dependencies>
     <dependency>
       <groupId>io.opentelemetry</groupId>
       <artifactId>opentelemetry-sdk</artifactId>
     </dependency>
     <dependency>
       <groupId>io.opentelemetry</groupId>
       <artifactId>opentelemetry-sdk-extension-autoconfigure</artifactId>
     </dependency>
     <dependency>
       <groupId>io.opentelemetry</groupId>
       <artifactId>opentelemetry-exporter-otlp</artifactId>
     </dependency>
     <dependency>
       <groupId>io.opentelemetry.instrumentation</groupId>
       <artifactId>opentelemetry-logback-appender-1.0</artifactId>
     </dependency>
   </dependencies>
   ```
2. Add the OpenTelemetry appender to `src/main/resources/logback.xml`:

   ```xml
   <configuration>
     <appender name="OTEL" class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender">
       <captureExperimentalAttributes>true</captureExperimentalAttributes>
       <captureKeyValuePairAttributes>true</captureKeyValuePairAttributes>
     </appender>
     <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
       <encoder>
         <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
       </encoder>
     </appender>
     <root level="INFO">
       <appender-ref ref="OTEL" />
       <appender-ref ref="CONSOLE" />
     </root>
   </configuration>
   ```
3. Initialize the SDK and install the appender at startup. The SDK reads the endpoint and headers from the environment variables above.

   ```java
   import io.opentelemetry.sdk.OpenTelemetrySdk;
   import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
   import io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender;
   import org.slf4j.Logger;
   import org.slf4j.LoggerFactory;

   public class Application {
       private static final Logger logger = LoggerFactory.getLogger(Application.class);

       public static void main(String[] args) {
           OpenTelemetrySdk sdk = AutoConfiguredOpenTelemetrySdk.initialize().getOpenTelemetrySdk();
           OpenTelemetryAppender.install(sdk);

           logger.info("Hello from Java");
       }
   }
   ```

#### Other languages

OpenTelemetry provides logs SDKs for other languages as well, at different maturity levels. SDKs that follow the OpenTelemetry specification read the [environment variables](#configure-the-exporter) above, so the setup is:

1. Install the OpenTelemetry logs SDK and OTLP log exporter for your language.
2. Create a `LoggerProvider` with a batch processor and the OTLP log exporter.
3. Connect your logging library through its OpenTelemetry bridge or appender.

See the [OpenTelemetry language documentation](https://opentelemetry.io/docs/languages/) 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](/features/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` |

> **Warning:** 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 Request` errors:** check the `authorization` value in `OTEL_EXPORTER_OTLP_HEADERS`. Middleware returns `400` for a missing or invalid API key.
- **Logs from short-lived processes are missing:** shut down the `LoggerProvider` (or call `forceFlush`) before the process exits.
- **Logs are not linked to traces:** make sure tracing is also set up. `trace_id` and `span_id` are 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 page or join our Slack channel.
