Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2

### :bug: Bug Fixes

* refactor(resources): use runtime check for default service name [#6257](https://github.com/open-telemetry/opentelemetry-js/pull/6257) @overbalance

### :books: Documentation

### :house: Internal
Expand Down
2 changes: 2 additions & 0 deletions bundler-tests/browser/nextjs-15-edge/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
package-lock=false
install-links=false
7 changes: 7 additions & 0 deletions bundler-tests/browser/nextjs-15-edge/app/layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
3 changes: 3 additions & 0 deletions bundler-tests/browser/nextjs-15-edge/app/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Home() {
return <h1>OTel Edge Runtime Test</h1>;
}
11 changes: 11 additions & 0 deletions bundler-tests/browser/nextjs-15-edge/middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { defaultServiceName } from '@opentelemetry/resources';

export function middleware(request) {
const serviceName = defaultServiceName();
console.log('Service name:', serviceName);
return Response.next();
}

export const config = {
matcher: '/api/:path*',
};
19 changes: 19 additions & 0 deletions bundler-tests/browser/nextjs-15-edge/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
webpack: (config, { dev }) => {
// Treat warnings as errors
config.plugins.push({
apply: compiler => {
compiler.hooks.done.tap('FailOnWarnings', stats => {
if (stats.compilation.warnings.length > 0) {
console.error(stats.compilation.warnings.join('\n'));
throw new Error('Webpack build has warnings');
}
});
},
});
return config;
},
};

export default nextConfig;
15 changes: 15 additions & 0 deletions bundler-tests/browser/nextjs-15-edge/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "nextjs-15-edge-bundle-test",
"type": "module",
"private": true,
"scripts": {
"build": "rm -rf .next && next build",
"test:bundle": "npm i && npm run build"
},
"dependencies": {
"@opentelemetry/resources": "file:../../../packages/opentelemetry-resources",
"next": "^15",
"react": "^18",
"react-dom": "^18"
}
}
3 changes: 3 additions & 0 deletions bundler-tests/browser/nextjs-15-edge/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "nextjs-15-edge-bundle-test"
}
2 changes: 2 additions & 0 deletions bundler-tests/browser/nextjs-16-edge/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
package-lock=false
install-links=false
10 changes: 10 additions & 0 deletions bundler-tests/browser/nextjs-16-edge/app/api/test/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { defaultServiceName } from '@opentelemetry/resources';

export const runtime = 'edge';

export function GET(request) {
const serviceName = defaultServiceName();
return new Response(JSON.stringify({ serviceName }), {
headers: { 'Content-Type': 'application/json' },
});
}
7 changes: 7 additions & 0 deletions bundler-tests/browser/nextjs-16-edge/app/layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
3 changes: 3 additions & 0 deletions bundler-tests/browser/nextjs-16-edge/app/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Home() {
return <h1>OTel Edge Runtime Test</h1>;
}
4 changes: 4 additions & 0 deletions bundler-tests/browser/nextjs-16-edge/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};

export default nextConfig;
22 changes: 22 additions & 0 deletions bundler-tests/browser/nextjs-16-edge/next.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Next.js 16+ uses Turbopack, which lacks callbacks for build errors or warnings.
* This script runs the build and fails if Turbopack reports any warnings.
*/
import { execSync } from 'node:child_process';
import path from 'node:path';

const testDir = path.dirname(new URL(import.meta.url).pathname);

// Install dependencies and run build, capturing both stdout and stderr
const output = execSync('npm run build 2>&1', {
cwd: testDir,
encoding: 'utf-8',
});

const hasWarning = /Turbopack build encountered/.test(output);
if (hasWarning) {
console.error('Build produced warnings:\n', output);
process.exit(1);
}

console.log('Build completed without warnings');
15 changes: 15 additions & 0 deletions bundler-tests/browser/nextjs-16-edge/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "nextjs-16-edge-bundle-test",
"type": "module",
"private": true,
"scripts": {
"build": "rm -rf .next && next build",
"test:bundle": "npm i && npx tsx next.test.ts"
},
"dependencies": {
"@opentelemetry/resources": "file:../../../packages/opentelemetry-resources",
"next": "^16",
"react": "^19",
"react-dom": "^19"
}
}
3 changes: 3 additions & 0 deletions bundler-tests/browser/nextjs-16-edge/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "nextjs-16-edge-bundle-test"
}
29 changes: 20 additions & 9 deletions packages/opentelemetry-resources/src/default-service-name.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,26 @@
* limitations under the License.
*/

// Check if we are in a Node.js environment and if so, use the process.argv0 property
// to determine the default service name
const DEFAULT_SERVICE_NAME =
typeof process === 'object' &&
typeof process.argv0 === 'string' &&
process.argv0.length > 0
? `unknown_service:${process.argv0}`
: 'unknown_service';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I asked at #6208 (comment) whether potentially using global.process?.argv0 would suffice to avoid whatever static analysis warnings.

It would also be nice to have specific examples of these static analysis warnings to help for motivation in future maintenance.

let serviceName: string | undefined;

/**
* Returns the default service name for OpenTelemetry resources.
* In Node.js environments, returns "unknown_service:<process.argv0>".
* In browser/edge environments, returns "unknown_service".
*/
export function defaultServiceName(): string {
return DEFAULT_SERVICE_NAME;
if (serviceName === undefined) {
try {
const argv0 = globalThis.process.argv0;
Copy link
Copy Markdown

@andreiborza andreiborza Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@overbalance this is actually still preventing our builds to go through 😓

we still get the same warnings as before:

./node_modules/.pnpm/@opentelemetry+resources@2.4.0_@opentelemetry+api@1.9.0/node_modules/@opentelemetry/resources/build/esm/default-service-name.js A Node.js API is used (process.argv0 at line: 19) which is not supported in the Edge Runtime.

Which I guess makes sense given this is statically analysed.

Oops, hasn't been released yet. My bad 😅.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries. Give it a shot now @andreiborza

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It worked: getsentry/sentry-javascript#18934

Thanks for taking care of this!

Copy link
Copy Markdown
Contributor Author

@overbalance overbalance Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw your comment on another PR about unclear Edge Runtime support. The Browser SIG has not discussed it but I'll bring it up on Slack to see if the JS SIG has. Feel free to join us there.

serviceName = argv0 ? `unknown_service:${argv0}` : 'unknown_service';
} catch {
serviceName = 'unknown_service';
}
}
return serviceName;
}

/** @internal For testing purposes only */
export function _clearDefaultServiceNameCache(): void {
serviceName = undefined;
}
3 changes: 3 additions & 0 deletions packages/opentelemetry-resources/test/Resource.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import * as assert from 'assert';
import * as sinon from 'sinon';
import { describeBrowser, describeNode } from './util';
import { defaultResource, emptyResource, resourceFromAttributes } from '../src';
import { _clearDefaultServiceNameCache } from '../src/default-service-name';
import * as EventEmitter from 'events';

describe('Resource', () => {
Expand All @@ -43,6 +44,8 @@ describe('Resource', () => {
'k8s.io/location': 'location1',
});

beforeEach(() => _clearDefaultServiceNameCache());

it('should return merged resource', () => {
const expectedResource = resourceFromAttributes({
'k8s.io/container/name': 'c1',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,49 @@
*/

import * as assert from 'assert';
import { defaultServiceName } from '../src/default-service-name';

const isNode = typeof process === 'object' && typeof process.argv0 === 'string';
import {
_clearDefaultServiceNameCache,
defaultServiceName,
} from '../src/default-service-name';
import { describeNode } from './util';

describe('defaultServiceName', () => {
const originalProcess = globalThis.process;

beforeEach(() => _clearDefaultServiceNameCache());
afterEach(() => {
globalThis.process = originalProcess;
});

it('returns unknown_service prefix', () => {
const serviceName = defaultServiceName();
assert.ok(serviceName.startsWith('unknown_service'));
});

if (isNode) {
it('returns consistent value on multiple calls', () => {
const serviceName1 = defaultServiceName();
const serviceName2 = defaultServiceName();
assert.strictEqual(serviceName1, serviceName2);
});

describeNode('defaultServiceName', () => {
it('includes process.argv0 in Node.js', () => {
const serviceName = defaultServiceName();
assert.ok(serviceName.startsWith('unknown_service:'));
assert.ok(serviceName.length > 'unknown_service:'.length);
assert.match(serviceName, /^unknown_service:.+/);
});
} else {
it('returns plain unknown_service in browser', () => {

it('returns plain unknown_service when process is not an object', () => {
// @ts-expect-error redefining process for testing
globalThis.process = undefined;
const serviceName = defaultServiceName();
assert.strictEqual(serviceName, 'unknown_service');
});
}

it('returns consistent value on multiple calls', () => {
const serviceName1 = defaultServiceName();
const serviceName2 = defaultServiceName();
assert.strictEqual(serviceName1, serviceName2);
it('returns plain unknown_service when process is stubbed but empty (edge runtimes)', () => {
// @ts-expect-error redefining process for testing
globalThis.process = {};
const serviceName = defaultServiceName();
assert.strictEqual(serviceName, 'unknown_service');
});
});
});