# How is logging handled in OpenWork: Structured JSON and Sentry Integration

> Discover how OpenWork manages logging with structured JSON output and integrates Sentry or OpenTelemetry. Learn about configuration and implementation details.

- Repository: [Different AI/openwork](https://github.com/different-ai/openwork)
- Tags: how-to-guide
- Published: 2026-08-13

---

**OpenWork handles logging through a structured JSON stdout logger with optional Sentry or OpenTelemetry backends, configured via environment variables and implemented across services using factory functions like `createJsonStdoutLogger` and `createAppLogger`.**

The OpenWork repository (`different-ai/openwork`) implements a centralized observability layer that separates log formatting from backend routing. At startup, services parse environment configuration to determine whether logs flow to stdout only, Sentry, or OpenTelemetry, ensuring consistent structured output while maintaining deployment flexibility.

## Core Logging Architecture

### Environment-Based Configuration

Logging behavior is determined at runtime by the `parseObservabilityEnv` function, which reads environment variables to select the backend type—`none`, `otel`, or `sentry`—and extracts service metadata. This configuration parser is located in the utilities package and invoked from service-specific entry points such as [`ee/apps/den-api/src/observability/config.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/observability/config.ts) [[source]](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-api/src/observability/config.ts).

### Structured JSON Logger Factory

The foundation of the logging stack is `createJsonStdoutLogger`, defined in [`ee/packages/utils/src/observability.ts`](https://github.com/different-ai/openwork/blob/main/ee/packages/utils/src/observability.ts) [[source]](https://github.com/different-ai/openwork/blob/dev/ee/packages/utils/src/observability.ts). This factory returns a logger instance that emits single-line JSON objects containing standard fields (`timestamp`, `level`, `service`, `message`) alongside arbitrary contextual data. The structured format enables automated parsing by log aggregation platforms without regex-based extraction.

## Application-Level Logger Implementation

### API Gateway Logger

Services wrap the base JSON logger with domain-specific sanitization and context enrichment. The Den API gateway uses `createAppLogger` from [`ee/apps/den-api/src/observability/logger.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/observability/logger.ts) [[source]](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-api/src/observability/logger.ts) to instantiate loggers that automatically inject service names and trace contexts. This wrapper sanitizes sensitive fields before emission while preserving the underlying JSON schema required by downstream processors.

### Den-Web Runtime Logger

The Den-Web UI maintains a global logger singleton via `denWebLogger` in [`ee/apps/den-web/observability/runtime-logger.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-web/observability/runtime-logger.ts) [[source]](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-web/observability/runtime-logger.ts). This module manages the active sink implementation and exposes `shutdownRetainedTelemetry`, which flushes buffered logs and traces during graceful shutdown on SIGTERM or SIGINT signals.

## Backend Integrations and Switching

### Sentry Integration

When `DEN_OBSERVABILITY_BACKEND` is set to `sentry`, the system invokes `initSentryRuntime` from [`ee/apps/den-web/observability/sentry-runtime.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-web/observability/sentry-runtime.ts) [[source]](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-web/observability/sentry-runtime.ts). This function wraps the default stdout sink with `logToSentry`, forwarding all log calls to the Sentry SDK while maintaining local JSON output. The integration captures error contexts, distributed traces, and log levels within the Sentry dashboard.

### Configuration via Environment Variables

Switching backends requires zero code changes—only environment variable updates. Setting `DEN_OBSERVABILITY_BACKEND=sentry` alongside `SENTRY_DSN` and optional `SENTRY_TRACES_SAMPLE_RATE` triggers the Sentry runtime initialization. The configuration validates these variables during startup, ensuring the appropriate backend is instantiated before application code executes.

## Practical Usage Examples

### Creating a Logger for a Custom Component

```typescript
import { createAppLogger } from "@openwork-ee/apps/den-api/src/observability/logger";

const logger = createAppLogger({ fields: { component: "my_custom_worker" } });

logger.info("worker started", { pid: process.pid });
logger.error("unexpected error", { error: err });

```

This factory uses the same JSON stdout logger defined in [`observability.ts`](https://github.com/different-ai/openwork/blob/main/observability.ts) and automatically inherits the service name from the parsed configuration.

### Switching the Backend to Sentry

```bash

# .env or exported shell variables

DEN_OBSERVABILITY_BACKEND=sentry
SENTRY_DSN=https://public_key@host/12345
SENTRY_TRACES_SAMPLE_RATE=0.05

```

Upon restart, `parseObservabilityEnv` creates a `SentryBackendObservabilityConfig`, and `initSentryRuntime` replaces the sink. Every subsequent `logger.info()` call routes to both Sentry and JSON stdout.

### Adding Contextual Fields with Child Loggers

```typescript
const requestLogger = logger.child({ requestId: req.id });
requestLogger.debug("received payload", { payloadSize: req.body.length });

```

The `child` method, implemented in `createJsonStdoutLogger`, performs shallow merges of parent and child fields.

### Emitting Logs from the Den-Web UI

```typescript
import { denWebLogger } from "./observability/runtime-logger";

denWebLogger.warn("session timeout", { userId: session.userId });

```

`denWebLogger` respects the current sink state—whether JSON stdout or Sentry—without requiring caller modifications.

## Summary

- **Structured JSON stdout** is the default logging format, implemented in [`observability.ts`](https://github.com/different-ai/openwork/blob/main/observability.ts) via `createJsonStdoutLogger`.
- **Backend routing** is controlled by `parseObservabilityEnv`, supporting `none`, `otel`, and `sentry` modes without code changes.
- **Application wrappers** like `createAppLogger` and `denWebLogger` provide sanitization and context injection.
- **Sentry integration** is activated through `initSentryRuntime`, which wraps the stdout sink with `logToSentry`.
- **Graceful shutdown** is handled by `shutdownRetainedTelemetry` to ensure buffered logs are flushed before process exit.

## Frequently Asked Questions

### How do I switch from stdout logging to Sentry in OpenWork?

Set the environment variable `DEN_OBSERVABILITY_BACKEND=sentry` and provide a valid `SENTRY_DSN`. The `initSentryRuntime` function automatically intercepts log calls and forwards them to Sentry while preserving local JSON output. No application code modifications are required.

### Where is the structured JSON logger defined in the OpenWork codebase?

The core implementation resides in [`ee/packages/utils/src/observability.ts`](https://github.com/different-ai/openwork/blob/main/ee/packages/utils/src/observability.ts) [[source]](https://github.com/different-ai/openwork/blob/dev/ee/packages/utils/src/observability.ts). This file exports `createJsonStdoutLogger`, which constructs the JSON-emitting logger used across all services.

### How does OpenWork handle log sanitization and context?

Services use wrapper factories such as `createAppLogger` (Den API) and `denWebLogger` (Den-Web) to sanitize sensitive fields and inject runtime context like service names and trace IDs. The `child` method allows request-scoped field injection without mutating the parent logger instance.

### What happens to logs during graceful shutdown?

The [`runtime-logger.ts`](https://github.com/different-ai/openwork/blob/main/runtime-logger.ts) module exposes `shutdownRetainedTelemetry`, which flushes any buffered logs and traces to the active backend (Sentry or stdout) when the process receives SIGTERM or SIGINT. This prevents log loss during container orchestration scaling events or deployments.