# How Openship BuildLogger Provides a Single Source of Truth for Log Emission

> Discover how Openship BuildLogger offers a single source of truth for log emission. Centralize build and deployment output with consistent formatting and progress tracking.

- Repository: [oblien/openship](https://github.com/oblien/openship)
- Tags: internals
- Published: 2026-07-30

---

**The Openship BuildLogger class centralizes all build and deployment output through a unified API that ensures consistent formatting, stream capture, and progress tracking across the entire pipeline.**

The [oblien/openship](https://github.com/oblien/openship) repository implements a centralized logging architecture through the **BuildLogger** class to eliminate fragmented log sources during container builds and remote deployments. Defined in [`packages/adapters/src/runtime/build-pipeline.ts`](https://github.com/oblien/openship/blob/main/packages/adapters/src/runtime/build-pipeline.ts), this class funnels every log entry through a single instance, maintaining strict consistency in timestamp formatting, error handling, and execution state reporting across asynchronous operations.

## Centralizing Log Emission Through BuildLogger

The `BuildLogger` class serves as the exclusive authority for all log output within the Openship runtime. Rather than scattering `console.log` statements throughout the codebase, every runtime module accepts an optional `BuildLogger` parameter that routes output through centralized methods. When a caller does not provide a logger instance, runtimes automatically instantiate a default logger using `new BuildLogger()`. This pattern ensures that every execution path, whether invoked from the CLI or programmatically, routes through the same logging infrastructure without requiring manual initialization at every call site.

## Core API Methods for Unified Logging

The `BuildLogger` exposes three primary methods that establish a strict contract for log emission across the system:

- **`log(message, level?)`** – Formats entries with consistent timestamps and level tags (`info`, `warn`, `error`), replacing ad-hoc formatting throughout the codebase.
- **`step(stepId, status, message?)`** – Records execution phases such as `deploy`, `build`, or `transfer`, enabling the UI to render coherent progress indicators.
- **`callback(entry)`** – Returns a function reference that streams raw output directly from subprocesses and remote executors back into the logger.

This API design establishes a **single source of truth** where all observability data originates from one instance, eliminating the risk of duplicate or missing log entries during complex multi-step operations.

## Integrating BuildLogger with Runtime Executors

Runtime modules throughout the `packages/adapters/src/runtime/` directory accept the logger instance to capture output from low-level operations. For example, file transfer operations in [`transfer.ts`](https://github.com/oblien/openship/blob/main/transfer.ts) and Docker builds in [`docker.ts`](https://github.com/oblien/openship/blob/main/docker.ts) receive the logger's callback to stream real-time output.

Consider how the logger integrates with executor methods:

```typescript
import { BuildLogger } from "./runtime/build-pipeline";

async function transferFiles(target, localPath, logger?: BuildLogger) {
  const log = logger ?? new BuildLogger();
  
  // Stream subprocess output directly into the centralized logger
  await target.executor.transferIn(
    localPath,
    target.path,
    log.callback,  // All raw output funnels here
    { /* options */ }
  );
}

```

By passing `logger.callback` to low-level executors responsible for SSH transfers or Docker daemon communication, Openship ensures that even binary stream output from external processes enters the same log sequence as application-level messages.

## Ensuring Consistent Formatting and Error Handling

The `BuildLogger` enforces uniform message formatting by intercepting every log entry before emission. When developers invoke `logger.log("Message", "error")`, the class automatically prefixes the output with timestamp and severity indicators, ensuring that error logs maintain the same structural consistency as informational messages.

This centralized approach to error handling prevents discrepancy between console output and programmatic logs. Rather than calling `console.error` directly, runtimes emit failures through:

```typescript
logger.log(`Docker build failed: ${msg}\n`, "error");

```

Because all errors route through the logger's `log` method, downstream consumers—including test suites in [`packages/adapters/test/deploy-pipeline.test.ts`](https://github.com/oblien/openship/blob/main/packages/adapters/test/deploy-pipeline.test.ts) and monitoring dashboards—receive identical error strings without format drift.

## Default Logger Pattern for Guaranteed Coverage

The codebase implements a defensive instantiation pattern that guarantees logging coverage even when callers omit the logger parameter. Each runtime function accepts an optional `BuildLogger` argument and immediately applies the nullish coalescing operator to create a default instance:

```typescript
async function buildImage(config: BuildConfig, logger?: BuildLogger) {
  const log = logger ?? new BuildLogger();
  log.log("Starting Docker build…\n");
  // ... build logic
}

```

This pattern ensures that every code path, whether tested in isolation or invoked through the deployment pipeline, maintains observability without requiring repetitive setup code. The optional parameter design allows dependency injection during testing while guaranteeing production logging through automatic instantiation.

## Summary

The Openship BuildLogger establishes a single source of truth for log emission by:

- **Centralizing output** through the `BuildLogger` class in [`packages/adapters/src/runtime/build-pipeline.ts`](https://github.com/oblien/openship/blob/main/packages/adapters/src/runtime/build-pipeline.ts), eliminating fragmented `console` calls across runtimes
- **Standardizing formatting** via the `log()` method, which applies consistent timestamps and severity tags to every entry
- **Capturing subprocess streams** by supplying `logger.callback` to low-level executors in [`transfer.ts`](https://github.com/oblien/openship/blob/main/transfer.ts) and [`docker.ts`](https://github.com/oblien/openship/blob/main/docker.ts)
- **Tracking execution state** through the `step()` method, which records pipeline phases for UI progress indicators
- **Ensuring universal coverage** through default instantiation patterns that create a logger instance when none is provided

## Frequently Asked Questions

### What happens if I don't pass a BuildLogger to a runtime function?

If you omit the `BuildLogger` parameter when calling runtime functions like those in [`packages/adapters/src/runtime/docker.ts`](https://github.com/oblien/openship/blob/main/packages/adapters/src/runtime/docker.ts), the function automatically creates a default instance using `new BuildLogger()`. This ensures all execution paths maintain observability without requiring manual logger initialization at every call site, while still allowing dependency injection for testing scenarios.

### How does BuildLogger capture output from Docker and SSH processes?

The `logger.callback` method provides a function reference that streams raw output directly from subprocesses back into the centralized log. When executing Docker builds or file transfers, runtimes pass this callback to low-level executors, ensuring that stdout and stderr from external processes enter the same formatted log stream as application messages.

### Can BuildLogger track the progress of multi-step deployments?

Yes. The `step(stepId, status, message?)` method records specific pipeline phases such as `build`, `transfer`, or `deploy`. By calling `logger.step("deploy", "running")` at phase initiation and `logger.step("deploy", "completed")` upon finish, the logger maintains a centralized record of execution state that UI components can query to render real-time progress indicators.

### How does centralizing logs through BuildLogger improve error debugging?

By routing all errors through `logger.log(errorMessage, "error")` rather than disparate `console.error` calls, the BuildLogger ensures that error messages include consistent timestamps and severity tags. This eliminates format drift between development console output and production logs, allowing automated monitoring tools and test suites in [`packages/adapters/test/deploy-pipeline.test.ts`](https://github.com/oblien/openship/blob/main/packages/adapters/test/deploy-pipeline.test.ts) to parse failures reliably using a single schema.