Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Live metrics span processor causes Invalid URL internal error in NextJS #32684

Open
1 of 6 tasks
AyronK opened this issue Jan 23, 2025 · 7 comments
Open
1 of 6 tasks

Live metrics span processor causes Invalid URL internal error in NextJS #32684

AyronK opened this issue Jan 23, 2025 · 7 comments
Assignees
Labels
customer-reported Issues that are reported by GitHub users external to the Azure organization. needs-team-attention Workflow: This issue needs attention from Azure service team or SDK team question The issue doesn't require a change to the product in order to be resolved. Most issues start as that

Comments

@AyronK
Copy link

AyronK commented Jan 23, 2025

  • Package Name: @azure/monitor-opentelemetry
  • Package Version: 1.8.0
  • Operating system:
  • nodejs
    • version: v20.17.0
  • browser
    • name/version:
  • typescript
    • version:
  • Is the bug related to documentation in

Describe the bug
A clear and concise description of what the bug is.

Enabling live metrics in useAzureMonitor causes NextJS app to critically fail whenever a live metrics connection is made in Azure Portal. The error happens regardless of Next version (tested both v15 and v14).

TypeError: Invalid URL
    at new URL (node:internal/url:797:36)
    at getRequestData (webpack-internal:///(instrument)/./node_modules/@azure/monitor-opentelemetry/dist-esm/src/metrics/quickpulse/utils.js:247:24)
    at getSpanData (webpack-internal:///(instrument)/./node_modules/@azure/monitor-opentelemetry/dist-esm/src/metrics/quickpulse/utils.js:218:16)
    at LiveMetrics.recordSpan (webpack-internal:///(instrument)/./node_modules/@azure/monitor-opentelemetry/dist-esm/src/metrics/quickpulse/liveMetrics.js:357:80)
    at MetricHandler.recordSpan (webpack-internal:///(instrument)/./node_modules/@azure/monitor-opentelemetry/dist-esm/src/metrics/handler.js:81:74)
    at AzureMonitorSpanProcessor.onEnd (webpack-internal:///(instrument)/./node_modules/@azure/monitor-opentelemetry/dist-esm/src/traces/spanProcessor.js:22:29)
    at MultiSpanProcessor.onEnd (webpack-internal:///(instrument)/./node_modules/@opentelemetry/sdk-trace-base/build/esm/MultiSpanProcessor.js:89:31)
    at Span.end (webpack-internal:///(instrument)/./node_modules/@opentelemetry/sdk-trace-base/build/esm/Span.js:227:29)
    at D:\Repositories\apoteka-front-store\Apoteka.FrontStore\Cms\node_modules\next\dist\server\lib\trace\tracer.js:144:34
    at async D:\Repositories\apoteka-front-store\Apoteka.FrontStore\Cms\node_modules\next\dist\server\dev\next-dev-server.js:339:20
    at async Span.traceAsyncFn (D:\Repositories\apoteka-front-store\Apoteka.FrontStore\Cms\node_modules\next\dist\trace\trace.js:154:20)
    at async DevServer.handleRequest (D:\Repositories\apoteka-front-store\Apoteka.FrontStore\Cms\node_modules\next\dist\server\dev\next-dev-server.js:336:24)
    at async invokeRender (D:\Repositories\apoteka-front-store\Apoteka.FrontStore\Cms\node_modules\next\dist\server\lib\router-server.js:173:21)
    at async requestHandlerImpl (D:\Repositories\apoteka-front-store\Apoteka.FrontStore\Cms\node_modules\next\dist\server\lib\router-server.js:386:24)
    at async Server.requestListener (D:\Repositories\apoteka-front-store\Apoteka.FrontStore\Cms\node_modules\next\dist\server\lib\start-server.js:141:13)

To Reproduce
Steps to reproduce the behavior:

  1. Setup a new NextJS app with instrumentation using @azure/monitor-opentelemetry and middleware
  2. Configure @azure/monitor-opentelemetry using connection string and enable live metrics
  3. Run the application (everything should be normal at this point)
  4. Go to Azure Portal and connect to live metrics
  5. Observe the error

Expected behavior
A clear and concise description of what you expected to happen.

Invalid URL and write after end internal error should not cause the application using @azure/monitor-opentelemetry to fail completely. In case of configuration error the plugin should gently handle the issue and close live metrics connection without failing the application using it.

Screenshots
If applicable, add screenshots to help explain your problem.

Image
Image

Additional context
Add any other context about the problem here.

Repository with minimal reproduction steps:

https://github.com/[AyronK/azure-open-telemetry-repro-steps](https://github.com/AyronK/azure-open-telemetry-repro-steps)

The problem must come from this method returning empty string in some cases

function getUrl(attributes: Attributes): string {
if (!attributes) {
return "";
}
const httpMethod = attributes[SEMATTRS_HTTP_METHOD];
if (httpMethod) {
const httpUrl = attributes[SEMATTRS_HTTP_URL];
if (httpUrl) {
return String(httpUrl);
} else {
const httpScheme = attributes[SEMATTRS_HTTP_SCHEME];
const httpTarget = attributes[SEMATTRS_HTTP_TARGET];
if (httpScheme && httpTarget) {
const httpHost = attributes[SEMATTRS_HTTP_HOST];
if (httpHost) {
return `${httpScheme}://${httpHost}${httpTarget}`;
} else {
const netPeerPort = attributes[SEMATTRS_NET_PEER_PORT];
if (netPeerPort) {
const netPeerName = attributes[SEMATTRS_NET_PEER_NAME];
if (netPeerName) {
return `${httpScheme}://${netPeerName}:${netPeerPort}${httpTarget}`;
} else {
const netPeerIp = attributes[SEMATTRS_NET_PEER_IP];
if (netPeerIp) {
return `${httpScheme}://${netPeerIp}:${netPeerPort}${httpTarget}`;
}
}
}
}
}
}
}
return "";
}
which causes then this code to fail on URL constructor
requestData.Url = getUrl(span.attributes);
const urlObj = new URL(requestData.Url);

@github-actions github-actions bot added customer-reported Issues that are reported by GitHub users external to the Azure organization. needs-triage Workflow: This is a new issue that needs to be triaged to the appropriate team. question The issue doesn't require a change to the product in order to be resolved. Most issues start as that labels Jan 23, 2025
@AyronK
Copy link
Author

AyronK commented Jan 23, 2025

I have made a workaround span processor which fixes the failing spans by appending http url if missing

function getUrl(attributes: Attributes): string {
  if (!attributes) {
    return "";
  }
  const httpMethod = attributes[SEMATTRS_HTTP_METHOD];
  if (httpMethod) {
    const httpUrl = attributes[SEMATTRS_HTTP_URL];
    if (httpUrl) {
      return String(httpUrl);
    } else {
      const httpScheme = attributes[SEMATTRS_HTTP_SCHEME];
      const httpTarget = attributes[SEMATTRS_HTTP_TARGET];
      if (httpScheme && httpTarget) {
        const httpHost = attributes[SEMATTRS_HTTP_HOST];
        if (httpHost) {
          return `${httpScheme}://${httpHost}${httpTarget}`;
        } else {
          const netPeerPort = attributes[SEMATTRS_NET_PEER_PORT];
          if (netPeerPort) {
            const netPeerName = attributes[SEMATTRS_NET_PEER_NAME];
            if (netPeerName) {
              return `${httpScheme}://${netPeerName}:${netPeerPort}${httpTarget}`;
            } else {
              const netPeerIp = attributes[SEMATTRS_NET_PEER_IP];
              if (netPeerIp) {
                return `${httpScheme}://${netPeerIp}:${netPeerPort}${httpTarget}`;
              }
            }
          }
        }
      }
    }
  }
  return "";
}

class TestSpansProcessor implements SpanProcessor {
  forceFlush(): Promise<void> {
    return Promise.resolve();
  }

  shutdown(): Promise<void> {
    return Promise.resolve();
  }

  onStart(span: ReadableSpan): void {
    if (
      !getUrl(span.attributes) &&
      (span.kind === SpanKind.SERVER || span.kind === SpanKind.CONSUMER)
    ) {
      span.attributes[SEMATTRS_HTTP_URL] = "http://localhost:3000/";
    }
  }

  onEnd(): void {
  }
}

@mpodwysocki mpodwysocki removed the needs-triage Workflow: This is a new issue that needs to be triaged to the appropriate team. label Jan 23, 2025
@github-actions github-actions bot added the needs-team-attention Workflow: This issue needs attention from Azure service team or SDK team label Jan 23, 2025
@sravanakinapally
Copy link

There is a similar issue when accessing live metrics in Azure Application Insight

   code: 'ERR_INVALID_URL',
   input: ''
 POST /api/auth/_log xxxxxx - You can only call end() on a span once. []
 TypeError: Invalid URL
     at new URL (node:internal/url:806:29)
     at getRequestData (/app/node_modules/.pnpm/@azure+monitor-opentelemetry@1.8.0/node_modules/@azure/monitor-opentelemetry/dist/index.js:2555:24)
    at getSpanData (/app/node_modules/.pnpm/@azure+monitor-opentelemetry@1.8.0/node_modules/@azure/monitor-opentelemetry/dist/index.js:2526:16)
     at LiveMetrics.recordSpan (/app/node_modules/.pnpm/@azure+monitor-opentelemetry@1.8.0/node_modules/@azure/monitor-opentelemetry/dist/index.js:3945:29)
     at MetricHandler.recordSpan (/app/node_modules/.pnpm/@azure+monitor-opentelemetry@1.8.0/node_modules/@azure/monitor-opentelemetry/dist/index.js:4371:74)
     at AzureMonitorSpanProcessor.onEnd (/app/node_modules/.pnpm/@azure+monitor-opentelemetry@1.8.0/node_modules/@azure/monitor-opentelemetry/dist/index.js:4406:29)

@JacksonWeber
Copy link
Member

Thank you @AyronK for reporting the issue and the excellent workaround for the moment. I'll work on getting a fix out and update this thread when it's available.

@stsiushkevich
Copy link

stsiushkevich commented Jan 26, 2025

Hello Guys! I attached @azure/monitor-opentelemetry by the same way, I coppied your approach in NextJS 15 app. I have an issue with tracing event, I don't receive any custom event in Azure. My code for tracing:

import { trace } from '@opentelemetry/api'

const tracer = trace.getTracer('fe-suburbantrans-web')

export type TEvent = {
    name: string
    attributes: Record<string, string | undefined>
}

const TEST_EVENT: TEvent = { name: 'TEST', attributes: { log: 'trace' } }

export function traceEvent({ name, attributes }: TEvent = TEST_EVENT) {
    const span = tracer.startSpan(name)

    const keys = Object.keys(attributes)

    for (const key of keys) {
        span.setAttribute(key, attributes[key])
    }

    span.addEvent(`[EVENT]${name}`)

    setTimeout(() => {
        span.end()
    }, 500)
}

ai-instrumentation.ts

/* eslint-disable react-hooks/rules-of-hooks */
import { useAzureMonitor } from "@azure/monitor-opentelemetry";
import {  } from "@azure/monitor-opentelemetry";
import { Resource } from "@opentelemetry/resources";
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";

export async function register() {
    if (!process.env.APPLICATION_INSIGHTS_CONNECTION_STRING) {
        return;
    }

    try {
        useAzureMonitor({
            azureMonitorExporterOptions: {
                connectionString: process.env.APPLICATION_INSIGHTS_CONNECTION_STRING,
            },
            resource: new Resource({
                [ATTR_SERVICE_NAME]: "TEST",
            }),
            enableLiveMetrics: true,
            enableStandardMetrics: true,
            browserSdkLoaderOptions: { enabled: false },
            enableTraceBasedSamplingForLogs: true,
        });
    } catch (e) {
        console.error(e);
    }
}

instrumentation.js

export async function register() {
    if (process.env.NEXT_RUNTIME === "nodejs") {
        const appInsights = await import("@shared/config/ai-instrumentation")
        await appInsights.register()
    }
}

src/app/api/auth/[...nextauth]/route.ts

import axios from 'axios'

import { AuthOptions } from "next-auth"

import CredentialsProvider from "next-auth/providers/credentials"

import { traceEvent } from "@shared/lib/utils/TelemetryUtils"

import { convertToFormData } from "@shared/lib/utils/ConvertUtils"

import {
    AuthUser,
    ServerResponseBody
} from "@shared/types"

import { config } from '../../../../config'

const LOGIN_URL = `${config.backendServer.url}/api/auth/login`

const nextAuthConfig: AuthOptions = {
    providers: [
        CredentialsProvider({
            id: 'credentials',
            name: 'Credentials',
            credentials: {
                username: { label: "Username", type: "text", placeholder: "jsmith", value: "administrator" },
                password: { label: "Password", type: "password", value: "admin" },
            },

            async authorize(credentials) {
                const data = convertToFormData({
                    login: credentials?.username,
                    password: credentials?.password,
                });

                console.log('+++[LOG][AUTH] Login')
                console.log('+++[LOG][AUTH] Credentials:', data)

                traceEvent({
                    name: '[LOG][AUTH] Login',
                    attributes: {
                        username: credentials?.username,
                        password: credentials?.password,
                    }
                })

                const response = await axios.post<ServerResponseBody<AuthUser>>(LOGIN_URL, data, {
                    headers: { 'Content-Type': 'application/json' }
                })

                const user = response.data.data

                console.log('+++[LOG][AUTH] AuthUser:', user)

                return { ...user, id: user.login, email: user.login }
            }
        })
    ],
    pages: {
        signIn: '/login',
    },
    session: { strategy: "jwt" },
    callbacks: {
        async jwt({ token, user }) {
            return { ...token, ...user }
        },
        async session({ session, token, user }) {
            session.user = { ...token, ...session.user }
            return session;
        }
    }

}

export default nextAuthConfig

Maybe I do something wrong?

@AyronK
Copy link
Author

AyronK commented Jan 27, 2025

@stsiushkevich I don't think this has anything to do with this issue, this is not even a minimal reproduction setup as you're calling the tracking code from yet another plugin - next-auth.

@JacksonWeber
Copy link
Member

@AyronK Fix is here: #32720 and will be rolled out in the next release.

@AyronK
Copy link
Author

AyronK commented Jan 29, 2025

@JacksonWeber amazing, thank you for quick response!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
customer-reported Issues that are reported by GitHub users external to the Azure organization. needs-team-attention Workflow: This issue needs attention from Azure service team or SDK team question The issue doesn't require a change to the product in order to be resolved. Most issues start as that
Projects
None yet
Development

No branches or pull requests

5 participants