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
3 changes: 3 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -971,6 +971,7 @@ The following span names were adjusted:
| `http.client`, `http.client.stream` | The request method and sanitized URL | `GET https://api.example.com/users/123` | The request method and the domain, or just the method if there is no domain | `GET api.example.com`, `GET` |
| `router` | Framework-specific, sometimes containing the raw URL | `/users/123`, `SvelteKit Route Change` | The span's `http.route`, or `Router` if the SDK has none | `/users/:id`, `Router` |
| `handler` | Framework-specific, often carrying the request method | `GET /users/:id`, `route-handler`, `getUser` | The span's `http.route`, or `Request handler` if the SDK has none | `/users/:id`, `Request handler` |
| `function.gcp` | The request method and path for HTTP functions, otherwise the trigger's event type | `POST /users`, `google.pubsub.topic.publish` | The function name, or `Serverless function execution` if the SDK cannot resolve one | `myFunction`, `Serverless function execution` |
| `graphql` | The graphql phase and, for operations, the operation name | `query GetUser`, `graphql.parse`, `graphql.resolve user.0.name` | The operation type, or the processing type where there is none | `GraphQL query`, `GraphQL parse`, `GraphQL resolve` |
| `gen_ai.chat`, `gen_ai.embeddings`, `gen_ai.generate_content` | `{operation} {model}`, or `{operation} unknown` if the model is missing | `chat gpt-4`, `chat unknown` | `{operation} {model}`, or `{operation}` if the model is missing | `chat gpt-4`, `chat` |
| `gen_ai.invoke_agent` | The LangChain chain name, prefixed with `chain` rather than the operation | `chain format_prompt`, `chain unknown_chain` | `{operation} {name}`, where the name is the span's `gen_ai.agent.name`, `gen_ai.pipeline.name` or `gen_ai.function_id`, in that order, or `{operation}` if the span carries none | `invoke_agent format_prompt`, `invoke_agent` |
Expand All @@ -986,6 +987,8 @@ The following span names were adjusted:
| `db` (supabase) | The query builder call and the table, or `auth <method>` for auth calls | `select(...) from(users)`, `auth signInWithPassword` | The operation and the table, or the dotted auth method | `select users`, `auth.signInWithPassword` |
| `db.query` (redis, ioredis) | The serialized command, with its arguments redacted, or `redis-<command>` on the diagnostics-channel path | `set test-key [1 other arguments]`, `redis-SET` | The operation and the connection, the operation and the redis function for `FCALL`/`FCALL_RO`, or `redis` when the SDK knows neither | `SET localhost:6379`, `fcall my_func`, `redis` |

GCP function spans additionally carry `faas.name`, the `gcp.function.context.*` fields of the trigger event, and `http.request.method` plus `url.path` for HTTP functions.

#### Filtering and sampling

When span streaming is enabled (i.e. by default) `ignoreSpans` is evaluated when a span **starts**, at which point a span might not yet have its final name:
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/tracing/spans/spanNames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ export const ROUTER_SPAN_NAME_FALLBACK = 'Router';
*/
export const REQUEST_HANDLER_SPAN_NAME_FALLBACK = 'Request handler';

/**
* Fallback name for serverless function execution spans when no better-suited span name is available.
* @see https://getsentry.github.io/sentry-conventions/names/#faas-serverless-function-execution
*/
export const SERVERLESS_FUNCTION_SPAN_NAME_FALLBACK = 'Serverless function execution';

/**
* The `cache.operation` attribute value each cache op carries. Cache span names are
* `cache.{{cache.operation}}`, so the op constant itself doubles as the low-cardinality span name.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
import { SENTRY_SEGMENT_NAME_SOURCE, FAAS_TRIGGER, SENTRY_OP } from '@sentry/conventions/attributes';
import {
SENTRY_SEGMENT_NAME_SOURCE,
FAAS_NAME,
FAAS_TRIGGER,
SENTRY_OP,
GCP_FUNCTION_CONTEXT_TYPE,
GCP_FUNCTION_CONTEXT_ID,
GCP_FUNCTION_CONTEXT_SOURCE,
GCP_FUNCTION_CONTEXT_SPECVERSION,
GCP_FUNCTION_CONTEXT_TIME,
} from '@sentry/conventions/attributes';
import { FUNCTION_GCP } from '@sentry/conventions/op';
import { debug, handleCallbackErrors, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';
import {
debug,
getClient,
handleCallbackErrors,
hasSpanStreamingEnabled,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SERVERLESS_FUNCTION_SPAN_NAME_FALLBACK,
} from '@sentry/core';
import { captureException, flush, getCurrentScope, startSpanManual } from '@sentry/node';
import { DEBUG_BUILD } from '../debug-build';
import { domainify, markEventUnhandled, proxyFunction } from '../utils';
import { domainify, getFunctionName, markEventUnhandled, proxyFunction } from '../utils';
import type { CloudEventFunction, CloudEventFunctionWithCallback, WrapperOptions } from './general';

export type CloudEventFunctionWrapperOptions = WrapperOptions;
Expand Down Expand Up @@ -31,14 +48,28 @@ function _wrapCloudEventFunction(
...wrapOptions,
};
return (context, callback) => {
const client = getClient();

const functionName = getFunctionName();
const name =
client && hasSpanStreamingEnabled(client)
? functionName || SERVERLESS_FUNCTION_SPAN_NAME_FALLBACK
: context.type || '<unknown>';

return startSpanManual(
{
name: context.type || '<unknown>',
name,
attributes: {
[SENTRY_OP]: FUNCTION_GCP,
[FAAS_NAME]: functionName,
[FAAS_TRIGGER]: 'cloud_event',
[SENTRY_SEGMENT_NAME_SOURCE]: 'component',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.serverless.gcp_cloud_event',
[GCP_FUNCTION_CONTEXT_TYPE]: context.type,
[GCP_FUNCTION_CONTEXT_ID]: context.id,
[GCP_FUNCTION_CONTEXT_SOURCE]: context.source,
[GCP_FUNCTION_CONTEXT_SPECVERSION]: context.specversion,
[GCP_FUNCTION_CONTEXT_TIME]: context.time,
},
},
span => {
Expand Down
39 changes: 34 additions & 5 deletions packages/google-cloud-serverless/src/gcpfunction/events.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
import { SENTRY_SEGMENT_NAME_SOURCE, FAAS_TRIGGER, SENTRY_OP } from '@sentry/conventions/attributes';
import {
SENTRY_SEGMENT_NAME_SOURCE,
FAAS_NAME,
FAAS_TRIGGER,
SENTRY_OP,
GCP_FUNCTION_CONTEXT_EVENT_TYPE,
GCP_FUNCTION_CONTEXT_EVENT_ID,
GCP_FUNCTION_CONTEXT_RESOURCE,
GCP_FUNCTION_CONTEXT_TIMESTAMP,
SENTRY_ORIGIN,
} from '@sentry/conventions/attributes';
import { FUNCTION_GCP } from '@sentry/conventions/op';
import { debug, handleCallbackErrors, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';
import {
debug,
getClient,
handleCallbackErrors,
hasSpanStreamingEnabled,
SERVERLESS_FUNCTION_SPAN_NAME_FALLBACK,
} from '@sentry/core';
import { captureException, flush, getCurrentScope, startSpanManual } from '@sentry/node';
import { DEBUG_BUILD } from '../debug-build';
import { domainify, markEventUnhandled, proxyFunction } from '../utils';
import { domainify, getFunctionName, markEventUnhandled, proxyFunction } from '../utils';
import type { EventFunction, EventFunctionWithCallback, WrapperOptions } from './general';

export type EventFunctionWrapperOptions = WrapperOptions;
Expand Down Expand Up @@ -34,14 +50,27 @@ function _wrapEventFunction<F extends EventFunction | EventFunctionWithCallback>
return (...eventFunctionArguments: Parameters<F>): ReturnType<F> | Promise<void> => {
const [data, context, callback] = eventFunctionArguments;

const client = getClient();

const functionName = getFunctionName();
const name =
client && hasSpanStreamingEnabled(client)
? functionName || SERVERLESS_FUNCTION_SPAN_NAME_FALLBACK
: context.eventType;

return startSpanManual(
{
name: context.eventType,
name,
attributes: {
[SENTRY_OP]: FUNCTION_GCP,
[FAAS_NAME]: functionName,
[FAAS_TRIGGER]: 'event',
[SENTRY_SEGMENT_NAME_SOURCE]: 'component',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.serverless.gcp_event',
[SENTRY_ORIGIN]: 'auto.function.serverless.gcp_event',
Comment thread
Lms24 marked this conversation as resolved.
[GCP_FUNCTION_CONTEXT_EVENT_TYPE]: context.eventType,
[GCP_FUNCTION_CONTEXT_EVENT_ID]: context.eventId,
[GCP_FUNCTION_CONTEXT_RESOURCE]: context.resource,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Object resource set as span attribute

Medium Severity

gcp.function.context.resource is written from context.resource with no type check. Legacy GCP event contexts often supply resource as an object (service, name, type), including Pub/Sub. setAttribute stores that object, so spans can carry a non-primitive attribute. The existing gcpContextIntegration already skips non-string resource values for this reason.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ae80636. Configure here.

[GCP_FUNCTION_CONTEXT_TIMESTAMP]: context.timestamp,
},
},
span => {
Expand Down
29 changes: 25 additions & 4 deletions packages/google-cloud-serverless/src/gcpfunction/http.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
import { SENTRY_SEGMENT_NAME_SOURCE, FAAS_TRIGGER, SENTRY_OP } from '@sentry/conventions/attributes';
import {
SENTRY_SEGMENT_NAME_SOURCE,
FAAS_NAME,
FAAS_TRIGGER,
HTTP_REQUEST_METHOD,
SENTRY_OP,
URL_PATH,
} from '@sentry/conventions/attributes';
import { FUNCTION_GCP } from '@sentry/conventions/op';
import {
debug,
getClient,
handleCallbackErrors,
hasSpanStreamingEnabled,
httpRequestToRequestData,
isString,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SERVERLESS_FUNCTION_SPAN_NAME_FALLBACK,
setHttpStatus,
stripUrlQueryAndFragment,
} from '@sentry/core';
import { captureException, continueTrace, flush, getCurrentScope, startSpanManual } from '@sentry/node';
import { DEBUG_BUILD } from '../debug-build';
import { domainify, markEventUnhandled, proxyFunction } from '../utils';
import { domainify, getFunctionName, markEventUnhandled, proxyFunction } from '../utils';
import type { HttpFunction, WrapperOptions } from './general';

/**
Expand Down Expand Up @@ -49,14 +59,25 @@ function _wrapHttpFunction(fn: HttpFunction, options: Partial<WrapperOptions>):
const normalizedRequest = httpRequestToRequestData(req);
getCurrentScope().setSDKProcessingMetadata({ normalizedRequest });

const functionName = getFunctionName();

const client = getClient();
const hasSpanStreaming = client && hasSpanStreamingEnabled(client);
const name = hasSpanStreaming ? functionName || SERVERLESS_FUNCTION_SPAN_NAME_FALLBACK : `${reqMethod} ${reqUrl}`;

return startSpanManual(
{
name: `${reqMethod} ${reqUrl}`,
name,
attributes: {
[SENTRY_OP]: FUNCTION_GCP,
[FAAS_NAME]: functionName,
Comment thread
cursor[bot] marked this conversation as resolved.
[FAAS_TRIGGER]: 'http',
[SENTRY_SEGMENT_NAME_SOURCE]: 'route',
[SENTRY_SEGMENT_NAME_SOURCE]: hasSpanStreaming ? 'component' : 'route',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.serverless.gcp_http',
// The method and path used to be the span name; they stay on the span so that
// information survives the low-cardinality rename.
[HTTP_REQUEST_METHOD]: reqMethod || undefined,
[URL_PATH]: reqUrl || undefined,
},
},
span => {
Expand Down
12 changes: 12 additions & 0 deletions packages/google-cloud-serverless/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,15 @@ export function markEventUnhandled(scope: Scope, type: string): Scope {

return scope;
}

/**
* Resolves the name of the currently executing cloud function.
*
* `FUNCTION_TARGET` ("the function to be executed") is set by GCP for every deployed function, and
* by the functions-framework when running locally, where `K_SERVICE` is absent. `K_SERVICE` is the
* Cloud Run service the function runs as; the two differ whenever the entry point is named
* separately from the service, as in `gcloud run deploy my-service --function myHandler`.
*/
export function getFunctionName(): string | undefined {
return process.env.FUNCTION_TARGET || process.env.K_SERVICE || undefined;
}
Loading
Loading