Skip to content

Vercel

Get started quickly by instrumenting your app with Vercel's OpenTelemetry wrapper. You can also custom instrumenting your app for more flexibility.

Sending telemetry directly from your app to a vendor is not recommended for high throughput or single threaded apps. We recommend hosting a local OpenTelemetry collectory to buffer and forward telemetry.

Send traces with Vercel's automatic instrumentation

This is the fastest way to send OpenTelemetry traces from a Vercel app. There are two mechanisms - either with environment variables, or with a configuration file. ENV VARs is the fastest but least flexible.

Set up an instrumentation.ts file. The next.js open telemetry page has additional configuration options.

[Fastest path] Sending via ENV VARS

1. Set experimental.instrumentationHook = true in your next.config.js file:

javascript
// next.config.js
module.exports = {
  experimental: {
    instrumentationHook: true,
  },
};
// next.config.js
module.exports = {
  experimental: {
    instrumentationHook: true,
  },
};

2. Install dependencies.

bash
npm i --save @vercel/otel
npm i --save @vercel/otel

3. Create a instrumentation.ts file in your root (or src) directory:

typescript
// instrumentation.ts
import { registerOTel } from "@vercel/otel";

export function register() {
  registerOTel("your-service-name");
}%
// instrumentation.ts
import { registerOTel } from "@vercel/otel";

export function register() {
  registerOTel("your-service-name");
}%

4. Configure ENV VARS inside of Vercel settings to send to Datable

Go to https://vercel.com/<team>/<project>/settings/environment-variables and set the following:

yaml
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_ENDPOINT=https://YOUR_DATABLE_HOST.dtbl.io:4318
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_ENDPOINT=https://YOUR_DATABLE_HOST.dtbl.io:4318

Which should look like this: Vercel Env Vars

5. Deploy your app to Vercel, and you should see traces in Datable. 🎉

[optional] Additional ENV VARS options for more control

Alternatively, instead of OTEL_EXPORTER_OTLP_PROTOCOL and OTEL_EXPORTER_OTLP_ENDPOINT from step 3, you can specify the trace protocol and endpoint. You would do this to send logs, metrics, and traces to different destinations.

Only use one. We recommend http/protobuf for backend, and http/json for web applications.

yaml
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://YOUR_DATABLE_HOST.dtbl.io:4318/v1/traces
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://YOUR_DATABLE_HOST.dtbl.io:4318/v1/traces
yaml
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/json
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://YOUR_DATABLE_HOST.dtbl.io:4318/v1/traces
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/json
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://YOUR_DATABLE_HOST.dtbl.io:4318/v1/traces
yaml
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=grpc
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://YOUR_DATABLE_HOST.dtbl.io:4317
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=grpc
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://YOUR_DATABLE_HOST.dtbl.io:4317

Defaults and additional options can be found on the OTel exporter page

Sending via configuration

If you need more control over sending your telemetry, or just don't like the magic of setting environment variables, you can define your own exporter. This is effectively the same as the ENV VAR method but explicit in it's settings.

1. Set experimental.instrumentationHook = true in your next.config.js file.

javascript
// next.config.js
module.exports = {
  experimental: {
    instrumentationHook: true,
  },
};
// next.config.js
module.exports = {
  experimental: {
    instrumentationHook: true,
  },
};

2. Install dependencies.

bash
npm i --save @opentelemetry/exporter-trace-otlp-proto \
@vercel/otel
npm i --save @opentelemetry/exporter-trace-otlp-proto \
@vercel/otel

3. Create a instrumentation.ts file in your root (or src) directory.

typescript
// instrumentation.ts
import { registerOTel } from "@vercel/otel";

export function register() {
  registerOTel("your-service-name");
}%
import { registerOTel } from "@vercel/otel";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";

export function register() {

  const spanCollectorOptions = {
    url: "https://YOUR_DATABLE_HOST.dtbl.io:4318/v1/traces",
  };

  registerOTel({
    serviceName: "your-project-name",
    traceExporter: new OTLPTraceExporter(spanCollectorOptions),
  });
}
// instrumentation.ts
import { registerOTel } from "@vercel/otel";

export function register() {
  registerOTel("your-service-name");
}%
import { registerOTel } from "@vercel/otel";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";

export function register() {

  const spanCollectorOptions = {
    url: "https://YOUR_DATABLE_HOST.dtbl.io:4318/v1/traces",
  };

  registerOTel({
    serviceName: "your-project-name",
    traceExporter: new OTLPTraceExporter(spanCollectorOptions),
  });
}

4. Deploy your app to Vercel, and you should see traces in Datable. 🎉

Sending traces with custom instrumentation

This path gives you the most control, but is also the most complex.

The open source OpenTelemetry libraries are not compatible with the edge runtime, so it's recommended to use the @vercel/otel package to send traces to Datable. If you do choose to use custom instrumentation with the vanilla OpenTelemetry libraries, we recommend adding a check to your instrumentation.ts file to ensure it only runs in the Node.js runtime.

1. Enable the instrumentation hook in your next.config.js file.

javascript
// next.config.js
module.exports = {
  experimental: {
    instrumentationHook: true,
  },
};
// next.config.js
module.exports = {
  experimental: {
    instrumentationHook: true,
  },
};

2. Create a instrumentation.ts file in your root (or src) directory.

typescript
// instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME === "nodejs") {
    await import("./tracer");
  }
}
// instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME === "nodejs") {
    await import("./tracer");
  }
}

3. Install dependencies

bash
npm i --save @opentelemetry/sdk-node \
@opentelemetry/exporter-trace-otlp-proto \
@opentelemetry/resources \
@opentelemetry/semantic-conventions \
@opentelemetry/sdk-trace-node
npm i --save @opentelemetry/sdk-node \
@opentelemetry/exporter-trace-otlp-proto \
@opentelemetry/resources \
@opentelemetry/semantic-conventions \
@opentelemetry/sdk-trace-node

4. Add the tracing library to your project. This will batch traces, and send them directly to Datable.

typescript
// tracer.ts
import { NodeSDK } from "@opentelemetry/sdk-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
import { Resource } from "@opentelemetry/resources";
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-node";

const spanCollectorOptions = {
  url: "https://YOUR_DATABLE_HOST.dtbl.io:4318/v1/traces",
};

const traceExporter = new OTLPTraceExporter(spanCollectorOptions);

const sdk = new NodeSDK({
  resource: new Resource({
    [SemanticResourceAttributes.SERVICE_NAME]: "next-app",
  }),
  spanProcessor: new BatchSpanProcessor(traceExporter),
});
sdk.start();
// tracer.ts
import { NodeSDK } from "@opentelemetry/sdk-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
import { Resource } from "@opentelemetry/resources";
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-node";

const spanCollectorOptions = {
  url: "https://YOUR_DATABLE_HOST.dtbl.io:4318/v1/traces",
};

const traceExporter = new OTLPTraceExporter(spanCollectorOptions);

const sdk = new NodeSDK({
  resource: new Resource({
    [SemanticResourceAttributes.SERVICE_NAME]: "next-app",
  }),
  spanProcessor: new BatchSpanProcessor(traceExporter),
});
sdk.start();

NextJS auto instruments itself, but for additional configurations see the nextjs examples

5. Deploy your app to Vercel, and you should see traces in Datable. 🎉

[Untested] Sending logs

🔥 Here be dragons 🔥

The OpenTelemetry Javascript SDK log agent support is not yet stable, and the following is untested in Vercel.

It's worth looking at the differences between sending logs for the web versus from nodejs in the Otel Log Exporter documentation.

As with traces, it's not recommended to send logs directly from your app to a vendor. We recommend hosting a local OpenTelemetry Collector or a log forwarder like FluentBit or Vector to collect, buffer, and forward logs.

That said, here's how you might send logs from your Vercel app to Datable:

Winston

Send logs via winston

1. Install the log dependencies

bash
npm i --save @opentelemetry/exporter-logs-otlp-http \
@opentelemetry/sdk-logs \
@opentelemetry/resources \
@opentelemetry/semantic-conventions \
@opentelemetry/api-logs
npm i --save @opentelemetry/exporter-logs-otlp-http \
@opentelemetry/sdk-logs \
@opentelemetry/resources \
@opentelemetry/semantic-conventions \
@opentelemetry/api-logs

2. Add a logger.ts file to your project

typescript
// logger.ts
import {
  LoggerProvider,
  BatchLogRecordProcessor,
} from "@opentelemetry/sdk-logs";
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
import { SeverityNumber } from "@opentelemetry/api-logs";
import { Resource } from "@opentelemetry/resources";

import { createLogger, transports } from "winston";

const resource = new Resource({
  [SemanticResourceAttributes.SERVICE_NAME]: "my app",
  [SemanticResourceAttributes.SERVICE_VERSION]: "0.0.1",
});

const logCollectorOptions = {
  url: "https://YOUR_DATABLE_HOST.dtbl.io:4318/v1/logs",
};
const logExporter = new OTLPLogExporter(logCollectorOptions);
const loggerProvider = new LoggerProvider({ resource });

loggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(logExporter));

const otelLogger = loggerProvider.getLogger("otel", "1.0.0");

// Adding Winston support
// Replace with your log framework, or use otelLogger.emit() directly
const formatLog = (args: any) => {
  typeof args === "string" ? args : JSON.stringify(args);
};

// your winston implementation
const consoleTransport = new transports.Console();
const logger = createLogger({
  transports: [consoleTransport],
});

const customLogger = {
  ...logger,
  info: (args: any) => {
    otelLogger.emit({
      body: formatLog(args),
      severityNumber: SeverityNumber.INFO,
    });
    return logger.info(args);
  },

  error: (args: any) => {
    otelLogger.emit({
      body: formatLog(args),
      severityNumber: SeverityNumber.ERROR,
    });
    return logger.error(args);
  },
};

export default customLogger;
// logger.ts
import {
  LoggerProvider,
  BatchLogRecordProcessor,
} from "@opentelemetry/sdk-logs";
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
import { SeverityNumber } from "@opentelemetry/api-logs";
import { Resource } from "@opentelemetry/resources";

import { createLogger, transports } from "winston";

const resource = new Resource({
  [SemanticResourceAttributes.SERVICE_NAME]: "my app",
  [SemanticResourceAttributes.SERVICE_VERSION]: "0.0.1",
});

const logCollectorOptions = {
  url: "https://YOUR_DATABLE_HOST.dtbl.io:4318/v1/logs",
};
const logExporter = new OTLPLogExporter(logCollectorOptions);
const loggerProvider = new LoggerProvider({ resource });

loggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(logExporter));

const otelLogger = loggerProvider.getLogger("otel", "1.0.0");

// Adding Winston support
// Replace with your log framework, or use otelLogger.emit() directly
const formatLog = (args: any) => {
  typeof args === "string" ? args : JSON.stringify(args);
};

// your winston implementation
const consoleTransport = new transports.Console();
const logger = createLogger({
  transports: [consoleTransport],
});

const customLogger = {
  ...logger,
  info: (args: any) => {
    otelLogger.emit({
      body: formatLog(args),
      severityNumber: SeverityNumber.INFO,
    });
    return logger.info(args);
  },

  error: (args: any) => {
    otelLogger.emit({
      body: formatLog(args),
      severityNumber: SeverityNumber.ERROR,
    });
    return logger.error(args);
  },
};

export default customLogger;

3. Register the logger

typescript
// instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME === "nodejs") {
    await import("./tracer");

    // Does this need to be in the nodejs runtime only?
    // Can it run on Edge and the Web?
    await import("./logger");
  }
}
// instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME === "nodejs") {
    await import("./tracer");

    // Does this need to be in the nodejs runtime only?
    // Can it run on Edge and the Web?
    await import("./logger");
  }
}

4. Use the logger inside your app This assumes invocation inside a nodejs runtime.

typescript
// myHelloWorld.ts
import logger from "./logger";

logger.info("Hello, world!");
// myHelloWorld.ts
import logger from "./logger";

logger.info("Hello, world!");

5. Deploy your app to Vercel, and you should see logs in Datable. 🎉

Pino

1. Install dependencies

bash
npm i @opentelemetry/exporter-logs-otlp-http \
@opentelemetry/sdk-logs \
@opentelemetry/resources \
@opentelemetry/semantic-conventions \
@opentelemetry/api-logs
npm i @opentelemetry/exporter-logs-otlp-http \
@opentelemetry/sdk-logs \
@opentelemetry/resources \
@opentelemetry/semantic-conventions \
@opentelemetry/api-logs

2. Add a logger.ts file to your project

typescript
// logger.ts
import {
  LoggerProvider,
  BatchLogRecordProcessor,
} from "@opentelemetry/sdk-logs";
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
import { SeverityNumber } from "@opentelemetry/api-logs";
import { Resource } from "@opentelemetry/resources";

import { createLogger, transports } from "winston";

const resource = new Resource({
  [SemanticResourceAttributes.SERVICE_NAME]: "my app",
  [SemanticResourceAttributes.SERVICE_VERSION]: "0.0.1",
});

const logCollectorOptions = {
  url: "https://YOUR_DATABLE_HOST.dtbl.io:4318/v1/logs",
};
const logExporter = new OTLPLogExporter(logCollectorOptions);
const loggerProvider = new LoggerProvider({ resource });

loggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(logExporter));

const otelLogger = loggerProvider.getLogger("otel", "1.0.0");

const pinoLogger = pino();

const formatLog = (args: any) =>
  typeof args === "string" ? args : JSON.stringify(args);

export const customLogger = {
  info: (args: any) => {
    otelLogger.emit({
      body: formatLog(args),
      severityNumber: SeverityNumber.INFO,
    });
    pinoLogger.info(args);
  },

  error: (args: any) => {
    otelLogger.emit({
      body: formatLog(args),
      severityNumber: SeverityNumber.ERROR,
    });
    pinoLogger.error(args);
  },
};
// logger.ts
import {
  LoggerProvider,
  BatchLogRecordProcessor,
} from "@opentelemetry/sdk-logs";
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
import { SeverityNumber } from "@opentelemetry/api-logs";
import { Resource } from "@opentelemetry/resources";

import { createLogger, transports } from "winston";

const resource = new Resource({
  [SemanticResourceAttributes.SERVICE_NAME]: "my app",
  [SemanticResourceAttributes.SERVICE_VERSION]: "0.0.1",
});

const logCollectorOptions = {
  url: "https://YOUR_DATABLE_HOST.dtbl.io:4318/v1/logs",
};
const logExporter = new OTLPLogExporter(logCollectorOptions);
const loggerProvider = new LoggerProvider({ resource });

loggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(logExporter));

const otelLogger = loggerProvider.getLogger("otel", "1.0.0");

const pinoLogger = pino();

const formatLog = (args: any) =>
  typeof args === "string" ? args : JSON.stringify(args);

export const customLogger = {
  info: (args: any) => {
    otelLogger.emit({
      body: formatLog(args),
      severityNumber: SeverityNumber.INFO,
    });
    pinoLogger.info(args);
  },

  error: (args: any) => {
    otelLogger.emit({
      body: formatLog(args),
      severityNumber: SeverityNumber.ERROR,
    });
    pinoLogger.error(args);
  },
};

3. Register the logger

typescript
// instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME === "nodejs") {
    await import("./tracer");
    // Does this need to be in the nodejs runtime only?
    // Can it run on Edge and the Web?
    await import("./logger");
  }
}
// instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME === "nodejs") {
    await import("./tracer");
    // Does this need to be in the nodejs runtime only?
    // Can it run on Edge and the Web?
    await import("./logger");
  }
}

4. Use the logger This assumes invocation inside a nodejs runtime.

typescript
// myHelloWorld.ts
import logger from "./logger";

logger.info("Hello, world!");
// myHelloWorld.ts
import logger from "./logger";

logger.info("Hello, world!");

5. Deploy your app to Vercel, and you should see logs in Datable. 🎉