Skip to content

[BUG]: Datadog APM Plugins Not Working in Next.js 15 - Missing Plugin spans in Prisma/http/aws/etc. #5945

@swordfish444

Description

@swordfish444

Tracer Version(s)

dd-trace-js#29704cf1d38586821323c4702e9d70ffca2250b6

Node.js Version(s)

22.15.0

Bug Report

Datadog APM Plugins Not Working in Next.js 15 - Missing Database & AWS Spans

Image

Problem Summary

We're experiencing an issue where Datadog APM tracing only shows 2 spans (web.request and next.request) but completely misses database operations (Prisma) and AWS SDK calls (S3 pre-signed URLs). The plugins appear to not be hooking into the middleware properly.

Environment & Architecture Details

Next.js Configuration

  • Next.js Version: 15.4.0-canary.84
  • Node.js Version: >=22.15.0
  • Package Manager: [email protected]
  • Module Type: ESM ("type": "module" in package.json)
  • TypeScript: 5.8.3 with "moduleResolution": "Bundler"

Key Build Configuration

// next.config.ts
export default {
  experimental: {
    nodeMiddleware: true,
  },
  serverExternalPackages: [
    '@datadog/native-metrics',
    '@datadog/pprof', 
    '@datadog/native-appsec',
    '@datadog/native-iast-taint-tracking',
    '@datadog/native-iast-rewriter',
    'graphql/language/visitor',
    'graphql/language/printer',
    'graphql/utilities'
  ],
  webpack: (config, { isServer }) => {
    if (isServer) {
      config.plugins = [...config.plugins, new PrismaPlugin()];
    }
    if (!isServer) {
      config.resolve.fallback = {
        ...config.resolve.fallback,
        'dd-trace': false, // Excluded from client bundle
      };
    }
    return config;
  }
};

Datadog Dependencies

{
  "dd-trace": "github:DataDog/dd-trace-js#29704cf1d38586821323c4702e9d70ffca2250b6",
  "@prisma/instrumentation": "6.10.1",
  "@opentelemetry/api": "1.8.0" // Overridden for compatibility
}

Database Setup (ZenStack + Prisma)

  • Database: PostgreSQL via Prisma
  • ORM: Prisma Client 6.10.1 with ZenStack 2.16.0 enhancement layer
  • Schema: Git submodule (prisma/ directory from separate repo)
  • Connection: Enhanced Prisma client with access control policies
// src/server/db/prisma.ts
const createBasePrismaClient = () => {
  const client = new PrismaClient({
    log: [{ emit: "event", level: "query" }],
  });
  
  // Cache invalidation extension applied
  return client.$extends({
    query: {
      $allModels: {
        async $allOperations({ operation, model, args, query }) {
          const result = await query(args);
          // Cache invalidation logic for user permissions
          return result;
        },
      },
    },
  });
};

Instrumentation Setup

// instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    const isTracingEnabled = process.env.DD_TRACE_ENABLED === 'true' ||
                            process.env.DATADOG_TRACE_ENABLED === 'true' ||
                            process.env.NODE_ENV === 'production';

    if (isTracingEnabled) {
      const ddTrace = await import('dd-trace');
      
      const tracerInstance = ddTrace.default.init({
        service: process.env.DD_SERVICE || 'patrol6-web',
        env: process.env.DD_ENV || process.env.NODE_ENV || 'development',
        version: process.env.DD_VERSION || process.env.APP_VERSION || 'unknown',
        sampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,
        runtimeMetrics: true,
        logInjection: process.env.DD_LOGS_INJECTION === 'true',
        startupLogs: process.env.DD_TRACE_STARTUP_LOGS === 'true',
        plugins: true, // Auto-discovery enabled
        // Agent configuration omitted for brevity
      });

      // Explicit plugin configuration
      tracerInstance.use('http', { service: process.env.DD_SERVICE, splitByDomain: true });
      tracerInstance.use('next', { service: process.env.DD_SERVICE });
      tracerInstance.use('pg', { service: `${process.env.DD_SERVICE}-db`, dbmPropagationMode: 'full' });
      tracerInstance.use('prisma', { service: `${process.env.DD_SERVICE}-db` });
      tracerInstance.use('fetch', { service: process.env.DD_SERVICE, splitByDomain: true });
      tracerInstance.use('aws-sdk', { service: `${process.env.DD_SERVICE}-aws` });
      tracerInstance.use('undici'); // Node.js 18+ fetch
    }

    // Prisma instrumentation (OpenTelemetry)
    const { PrismaInstrumentation, registerInstrumentations } = await import('@prisma/instrumentation');
    registerInstrumentations({
      instrumentations: [
        new PrismaInstrumentation({
          enabled: true,
          middleware: true
        }),
      ],
    });
  }
}

Build Process

  • Build Command: WEBPACK_CACHE_MEMORY_LIMIT=100000000 next build
  • Experimental Build Modes: Using --experimental-build-mode compile/generate
  • Asset Building: Custom webpack configuration with Prisma plugin
  • ESM Stages: Full ESM with "moduleResolution": "Bundler"

What We've Tried

  1. Multiple dd-trace initialization strategies:

    • Auto-discovery (plugins: true)
    • Explicit plugin configuration
    • Both OpenTelemetry + dd-trace hybrid approach
  2. Different import patterns:

    • Dynamic imports in instrumentation.ts
    • Static imports with conditional initialization
    • Tried both require() and import() syntax
    • Using NODE_OPTIONS='--require dd-trace/init'
  3. Plugin configuration variations:

    • Enabled/disabled auto-discovery
    • Explicit service naming per plugin
    • Different sampling rates and configuration options
  4. Prisma instrumentation approaches:

Expected vs Actual Behavior

Expected: Traces should show spans for:

  • HTTP requests (web.request) ✅
  • Next.js routing (next.request) ✅
  • Database queries via Prisma (prisma.* or pg.*) ❌
  • AWS S3 operations (aws-sdk.*) ❌
  • External HTTP calls (fetch.* or http.*) ❌

Actual: Only getting the first two spans, missing all database and external service calls.

Questions for the Community

  1. ESM Compatibility: Are there known issues with dd-trace plugin auto-discovery in Next.js 15 with full ESM?

  2. Instrumentation Timing: Could the dynamic imports in instrumentation.ts be causing plugins to initialize after the modules they need to instrument?

  3. ZenStack Interference: Could the Prisma client extensions/enhancements be preventing dd-trace from properly hooking into database operations?

  4. Next.js 15 Canary: Are there specific compatibility issues with the canary build we're using?

  5. Module Resolution: Could "moduleResolution": "Bundler" be affecting how dd-trace discovers and loads plugins?

Any insights on what might be preventing the plugins from working would be greatly appreciated! We're particularly interested in understanding if this is a configuration issue, a compatibility problem, or if there are additional steps needed for Next.js 15 + ESM + complex database setups.


This post provides all the technical details someone would need to understand your specific setup and help troubleshoot the plugin issues. The combination of Next.js 15 canary, full ESM, ZenStack enhancements, and the specific build configuration creates a complex environment that might be causing the plugin discovery/hooking issues.

Reproduction Code

No response

Error Logs

No response

Tracer Config

No response

Operating System

No response

Bundling

Unsure

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions