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
6 changes: 6 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -1112,6 +1112,12 @@ The experimental opt-in this replaces was removed:
+ sentryCloudflareVitePlugin();
```

### Cloudflare: rate limiter bindings no longer emit spans

Affected SDKs: `@sentry/cloudflare`.

Calls to rate limiter bindings (`env.MY_RATE_LIMITER.limit()`) no longer create a span. The removed span had the op `rpc`, the origin `auto.faas.cloudflare.rate_limit`, and the attribute `rpc.service: cloudflare.rate_limit`. Remove any dashboard, alert, or `ignoreSpans` entry that references it.

### `@sentry/ember` is now a v2 addon with manual setup

Affected SDKs: `@sentry/ember`.
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
isJSRPC,
isQueue,
isR2Bucket,
isRateLimit,
} from '../../utils/isBinding';
import { instrumentD1 } from './instrumentD1';
import { appendRpcMeta } from '../../utils/rpcMeta';
Expand All @@ -17,7 +16,6 @@ import { instrumentDurableObjectNamespace, STUB_NON_RPC_METHODS } from '../instr
import { instrumentFetcher } from './instrumentFetcher';
import { instrumentQueueProducer } from './instrumentQueueProducer';
import { instrumentR2Bucket } from './instrumentR2';
import { instrumentRateLimit } from './instrumentRateLimit';

function isProxyable(item: unknown): item is object {
return isObjectLike(item) || typeof item === 'function';
Expand All @@ -34,7 +32,6 @@ const instrumentedBindings = new WeakMap<object, unknown>();
* - Service bindings / JSRPC proxies
* - Queue producers (via `send` + `sendBatch` duck-typing)
* - R2 Buckets (via `head` + `put` + `createMultipartUpload` duck-typing)
* - Rate limiters (via `limit` duck-typing)
* - Workers AI (via `run` + `gateway` + `toMarkdown` duck-typing)
*
* @param env - The Cloudflare env object to instrument
Expand Down Expand Up @@ -81,13 +78,6 @@ export function instrumentEnv<Env extends Record<string, unknown>>(env: Env, opt
return instrumented;
}

if (isRateLimit(item)) {
const bindingName = typeof prop === 'string' ? prop : String(prop);
const instrumented = instrumentRateLimit(item, bindingName);
instrumentedBindings.set(item, instrumented);
return instrumented;
}

if (isAiBinding(item)) {
const instrumented = instrumentWorkersAiClient(item);
instrumentedBindings.set(item, instrumented);
Expand Down

This file was deleted.

13 changes: 1 addition & 12 deletions packages/cloudflare/src/utils/isBinding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

import type { Ai, D1Database, DurableObjectNamespace, Queue, R2Bucket, RateLimit } from '@cloudflare/workers-types';
import type { Ai, D1Database, DurableObjectNamespace, Queue, R2Bucket } from '@cloudflare/workers-types';

/**
* Checks if a value is a JSRPC proxy (service binding).
Expand Down Expand Up @@ -109,14 +109,3 @@ export function isR2Bucket(item: unknown): item is R2Bucket {
typeof item.createMultipartUpload === 'function'
);
}

/**
* Duck-type check for RateLimit bindings.
* RateLimit only exposes a single `limit` method. Because that is a fairly
* common method name, this check is intentionally run after the more specific
* binding checks (Queue, R2, D1) in `instrumentEnv`, so those win when a binding
* also happens to expose `limit`.
*/
export function isRateLimit(item: unknown): item is RateLimit {
return item != null && isNotJSRPC(item) && typeof item.limit === 'function';
}
28 changes: 0 additions & 28 deletions packages/cloudflare/test/instrumentations/instrumentEnv.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,34 +247,6 @@ describe('instrumentEnv', () => {
expect(instrumentDurableObjectNamespace).toHaveBeenCalledWith(doNamespace, true);
});

it('wraps RateLimit bindings in a proxy and forwards calls', async () => {
const startSpanSpy = vi.spyOn(SentryCore, 'startSpan');
const limit = vi.fn().mockResolvedValue({ success: true });
const rateLimiter = { limit };
const env = { MY_RATE_LIMITER: rateLimiter };
const instrumented = instrumentEnv(env);

const wrapped = instrumented.MY_RATE_LIMITER as typeof rateLimiter;
// Wrapped binding is a Proxy, not the original reference
expect(wrapped).not.toBe(rateLimiter);

const outcome = await wrapped.limit({ key: 'user-123' });
expect(outcome).toEqual({ success: true });
expect(limit).toHaveBeenCalledTimes(1);
expect(startSpanSpy).toHaveBeenCalledWith(
expect.objectContaining({ name: 'rate_limit MY_RATE_LIMITER' }),
expect.any(Function),
);
});

it('caches the wrapped RateLimit binding across repeated access', () => {
const rateLimiter = { limit: vi.fn() };
const env = { MY_RATE_LIMITER: rateLimiter };
const instrumented = instrumentEnv(env);

expect(instrumented.MY_RATE_LIMITER).toBe(instrumented.MY_RATE_LIMITER);
});

describe('Workers AI bindings', () => {
function createMockAiBinding() {
return {
Expand Down

This file was deleted.

33 changes: 1 addition & 32 deletions packages/cloudflare/test/utils/isBinding.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
import { isD1Database, isDurableObjectNamespace, isJSRPC, isQueue, isRateLimit } from '../../src/utils/isBinding';
import { isD1Database, isDurableObjectNamespace, isJSRPC, isQueue } from '../../src/utils/isBinding';

describe('isJSRPC', () => {
it('returns false for a plain object', () => {
Expand Down Expand Up @@ -210,34 +210,3 @@ describe('isD1Database', () => {
expect(isD1Database(jsrpcProxy)).toBe(false);
});
});

describe('isRateLimit', () => {
it('returns true for an object with a limit method', () => {
expect(isRateLimit({ limit: async () => ({ success: true }) })).toBe(true);
});

it('returns false when limit is missing', () => {
expect(isRateLimit({ foo: 'bar' })).toBe(false);
});

it('returns false when limit is not a function', () => {
expect(isRateLimit({ limit: 'nope' })).toBe(false);
});

it('returns false for null and undefined', () => {
expect(isRateLimit(null)).toBe(false);
expect(isRateLimit(undefined)).toBe(false);
});

it('returns false for a JSRPC proxy even though it returns a function for limit', () => {
const jsrpcProxy = new Proxy(
{},
{
get(_target, _prop) {
return () => {};
},
},
);
expect(isRateLimit(jsrpcProxy)).toBe(false);
});
});
Loading