Skip to content

Commit

Permalink
Merge pull request #9849 from getsentry/prepare-release/7.88.0
Browse files Browse the repository at this point in the history
meta(changelog): Update changelog for 7.88.0
  • Loading branch information
AbhiPrasad authored Dec 14, 2023
2 parents 56845fb + 8b6af6f commit b3269ca
Show file tree
Hide file tree
Showing 32 changed files with 13,638 additions and 234 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ jobs:
- name: Set up Deno
uses: denoland/setup-deno@v1.1.3
with:
deno-version: v1.37.1
deno-version: v1.38.5
- name: Restore caches
uses: ./.github/actions/restore-cache
env:
Expand Down
59 changes: 59 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,65 @@

- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott

## 7.88.0

### Important Changes

- **feat(browser): Add browser metrics sdk (#9794)**

The release adds alpha support for [Sentry developer metrics](https://github.com/getsentry/sentry/discussions/58584) in the Browser SDKs (`@sentry/browser` and related framework SDKs). Via the newly introduced APIs, you can now flush metrics directly to Sentry.

To enable capturing metrics, you first need to add the `MetricsAggregator` integration.

```js
Sentry.init({
dsn: '__DSN__',
integrations: [
new Sentry.metrics.MetricsAggregator(),
],
});
```

Then you'll be able to add `counters`, `sets`, `distributions`, and `gauges` under the `Sentry.metrics` namespace.

```js
// Add 4 to a counter named `hits`
Sentry.metrics.increment('hits', 4);

// Add 2 to gauge named `parallel_requests`, tagged with `happy: "no"`
Sentry.metrics.gauge('parallel_requests', 2, { tags: { happy: 'no' } });

// Add 4.6 to a distribution named `response_time` with unit seconds
Sentry.metrics.distribution('response_time', 4.6, { unit: 'seconds' });

// Add 2 to a set named `valuable.ids`
Sentry.metrics.set('valuable.ids', 2);
```

In a future release we'll add support for server runtimes (Node, Deno, Bun, Vercel Edge, etc.)

- **feat(deno): Optionally instrument `Deno.cron` (#9808)**

This releases add support for instrumenting [Deno cron's](https://deno.com/blog/cron) with [Sentry cron monitors](https://docs.sentry.io/product/crons/). This requires v1.38 of Deno run with the `--unstable` flag and the usage of the `DenoCron` Sentry integration.

```ts
// Import from the Deno registry
import * as Sentry from "https://deno.land/x/sentry/index.mjs";

Sentry.init({
dsn: '__DSN__',
integrations: [
new Sentry.DenoCron(),
],
});
```

### Other Changes

- feat(replay): Bump `rrweb` to 2.6.0 (#9847)
- fix(nextjs): Guard against injecting multiple times (#9807)
- ref(remix): Bump Sentry CLI to ^2.23.0 (#9773)

## 7.87.0

- feat: Add top level `getCurrentScope()` method (#9800)
Expand Down
1 change: 1 addition & 0 deletions packages/browser/src/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export {
withScope,
FunctionToString,
InboundFilters,
metrics,
} from '@sentry/core';

export { WINDOW } from './helpers';
Expand Down
29 changes: 29 additions & 0 deletions packages/core/src/baseclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import type {
FeedbackEvent,
Integration,
IntegrationClass,
MetricBucketItem,
MetricsAggregator,
Outcome,
PropagationContext,
SdkMetadata,
Expand Down Expand Up @@ -49,6 +51,7 @@ import { createEventEnvelope, createSessionEnvelope } from './envelope';
import { getCurrentHub } from './hub';
import type { IntegrationIndex } from './integration';
import { setupIntegration, setupIntegrations } from './integration';
import { createMetricEnvelope } from './metrics/envelope';
import type { Scope } from './scope';
import { updateSession } from './session';
import { getDynamicSamplingContextFromClient } from './tracing/dynamicSamplingContext';
Expand Down Expand Up @@ -88,6 +91,13 @@ const ALREADY_SEEN_ERROR = "Not capturing exception because it's already been ca
* }
*/
export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
/**
* A reference to a metrics aggregator
*
* @experimental Note this is alpha API. It may experience breaking changes in the future.
*/
public metricsAggregator?: MetricsAggregator;

/** Options passed to the SDK. */
protected readonly _options: O;

Expand Down Expand Up @@ -264,6 +274,9 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
public flush(timeout?: number): PromiseLike<boolean> {
const transport = this._transport;
if (transport) {
if (this.metricsAggregator) {
this.metricsAggregator.flush();
}
return this._isClientDoneProcessing(timeout).then(clientFinished => {
return transport.flush(timeout).then(transportFlushed => clientFinished && transportFlushed);
});
Expand All @@ -278,6 +291,9 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
public close(timeout?: number): PromiseLike<boolean> {
return this.flush(timeout).then(result => {
this.getOptions().enabled = false;
if (this.metricsAggregator) {
this.metricsAggregator.close();
}
return result;
});
}
Expand Down Expand Up @@ -383,6 +399,19 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
}
}

/**
* @inheritDoc
*/
public captureAggregateMetrics(metricBucketItems: Array<MetricBucketItem>): void {
const metricsEnvelope = createMetricEnvelope(
metricBucketItems,
this._dsn,
this._options._metadata,
this._options.tunnel,
);
void this._sendEnvelope(metricsEnvelope);
}

// Keep on() & emit() signatures in sync with types' client.ts interface
/* eslint-disable @typescript-eslint/unified-signatures */

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,6 @@ export { DEFAULT_ENVIRONMENT } from './constants';
export { ModuleMetadata } from './integrations/metadata';
export { RequestData } from './integrations/requestdata';
import * as Integrations from './integrations';
export { metrics } from './metrics/exports';

export { Integrations };
21 changes: 12 additions & 9 deletions packages/core/src/integrations/metadata.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { EventItem, EventProcessor, Hub, Integration } from '@sentry/types';
import type { Client, Event, EventItem, EventProcessor, Hub, Integration } from '@sentry/types';
import { forEachEnvelopeItem } from '@sentry/utils';

import { addMetadataToStackFrames, stripMetadataFromStackFrames } from '../metadata';
Expand Down Expand Up @@ -30,10 +30,13 @@ export class ModuleMetadata implements Integration {
/**
* @inheritDoc
*/
public setupOnce(addGlobalEventProcessor: (processor: EventProcessor) => void, getCurrentHub: () => Hub): void {
const client = getCurrentHub().getClient();
public setupOnce(_addGlobalEventProcessor: (processor: EventProcessor) => void, _getCurrentHub: () => Hub): void {
// noop
}

if (!client || typeof client.on !== 'function') {
/** @inheritDoc */
public setup(client: Client): void {
if (typeof client.on !== 'function') {
return;
}

Expand All @@ -50,12 +53,12 @@ export class ModuleMetadata implements Integration {
}
});
});
}

/** @inheritDoc */
public processEvent(event: Event, _hint: unknown, client: Client): Event {
const stackParser = client.getOptions().stackParser;

addGlobalEventProcessor(event => {
addMetadataToStackFrames(stackParser, event);
return event;
});
addMetadataToStackFrames(stackParser, event);
return event;
}
}
101 changes: 51 additions & 50 deletions packages/core/src/integrations/requestdata.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Event, EventProcessor, Hub, Integration, PolymorphicRequest, Transaction } from '@sentry/types';
import type { Client, Event, EventProcessor, Hub, Integration, PolymorphicRequest, Transaction } from '@sentry/types';
import type { AddRequestDataToEventOptions, TransactionNamingScheme } from '@sentry/utils';
import { addRequestDataToEvent, extractPathForTransaction } from '@sentry/utils';

Expand Down Expand Up @@ -95,65 +95,66 @@ export class RequestData implements Integration {
/**
* @inheritDoc
*/
public setupOnce(addGlobalEventProcessor: (eventProcessor: EventProcessor) => void, getCurrentHub: () => Hub): void {
public setupOnce(
_addGlobalEventProcessor: (eventProcessor: EventProcessor) => void,
_getCurrentHub: () => Hub,
): void {
// noop
}

/** @inheritdoc */
public processEvent(event: Event, _hint: unknown, client: Client): Event {
// Note: In the long run, most of the logic here should probably move into the request data utility functions. For
// the moment it lives here, though, until https://github.com/getsentry/sentry-javascript/issues/5718 is addressed.
// (TL;DR: Those functions touch many parts of the repo in many different ways, and need to be clened up. Once
// that's happened, it will be easier to add this logic in without worrying about unexpected side effects.)
const { transactionNamingScheme } = this._options;

addGlobalEventProcessor(event => {
const hub = getCurrentHub();
const self = hub.getIntegration(RequestData);

const { sdkProcessingMetadata = {} } = event;
const req = sdkProcessingMetadata.request;
const { sdkProcessingMetadata = {} } = event;
const req = sdkProcessingMetadata.request;

// If the globally installed instance of this integration isn't associated with the current hub, `self` will be
// undefined
if (!self || !req) {
return event;
}
if (!req) {
return event;
}

// The Express request handler takes a similar `include` option to that which can be passed to this integration.
// If passed there, we store it in `sdkProcessingMetadata`. TODO(v8): Force express and GCP people to use this
// integration, so that all of this passing and conversion isn't necessary
const addRequestDataOptions =
sdkProcessingMetadata.requestDataOptionsFromExpressHandler ||
sdkProcessingMetadata.requestDataOptionsFromGCPWrapper ||
convertReqDataIntegrationOptsToAddReqDataOpts(this._options);
// The Express request handler takes a similar `include` option to that which can be passed to this integration.
// If passed there, we store it in `sdkProcessingMetadata`. TODO(v8): Force express and GCP people to use this
// integration, so that all of this passing and conversion isn't necessary
const addRequestDataOptions =
sdkProcessingMetadata.requestDataOptionsFromExpressHandler ||
sdkProcessingMetadata.requestDataOptionsFromGCPWrapper ||
convertReqDataIntegrationOptsToAddReqDataOpts(this._options);

const processedEvent = this._addRequestData(event, req, addRequestDataOptions);
const processedEvent = this._addRequestData(event, req, addRequestDataOptions);

// Transaction events already have the right `transaction` value
if (event.type === 'transaction' || transactionNamingScheme === 'handler') {
return processedEvent;
}
// Transaction events already have the right `transaction` value
if (event.type === 'transaction' || transactionNamingScheme === 'handler') {
return processedEvent;
}

// In all other cases, use the request's associated transaction (if any) to overwrite the event's `transaction`
// value with a high-quality one
const reqWithTransaction = req as { _sentryTransaction?: Transaction };
const transaction = reqWithTransaction._sentryTransaction;
if (transaction) {
// TODO (v8): Remove the nextjs check and just base it on `transactionNamingScheme` for all SDKs. (We have to
// keep it the way it is for the moment, because changing the names of transactions in Sentry has the potential
// to break things like alert rules.)
const shouldIncludeMethodInTransactionName =
getSDKName(hub) === 'sentry.javascript.nextjs'
? transaction.name.startsWith('/api')
: transactionNamingScheme !== 'path';

const [transactionValue] = extractPathForTransaction(req, {
path: true,
method: shouldIncludeMethodInTransactionName,
customRoute: transaction.name,
});

processedEvent.transaction = transactionValue;
}
// In all other cases, use the request's associated transaction (if any) to overwrite the event's `transaction`
// value with a high-quality one
const reqWithTransaction = req as { _sentryTransaction?: Transaction };
const transaction = reqWithTransaction._sentryTransaction;
if (transaction) {
// TODO (v8): Remove the nextjs check and just base it on `transactionNamingScheme` for all SDKs. (We have to
// keep it the way it is for the moment, because changing the names of transactions in Sentry has the potential
// to break things like alert rules.)
const shouldIncludeMethodInTransactionName =
getSDKName(client) === 'sentry.javascript.nextjs'
? transaction.name.startsWith('/api')
: transactionNamingScheme !== 'path';

const [transactionValue] = extractPathForTransaction(req, {
path: true,
method: shouldIncludeMethodInTransactionName,
customRoute: transaction.name,
});

processedEvent.transaction = transactionValue;
}

return processedEvent;
});
return processedEvent;
}
}

Expand Down Expand Up @@ -199,12 +200,12 @@ function convertReqDataIntegrationOptsToAddReqDataOpts(
};
}

function getSDKName(hub: Hub): string | undefined {
function getSDKName(client: Client): string | undefined {
try {
// For a long chain like this, it's fewer bytes to combine a try-catch with assuming everything is there than to
// write out a long chain of `a && a.b && a.b.c && ...`
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return hub.getClient()!.getOptions()!._metadata!.sdk!.name;
return client.getOptions()._metadata!.sdk!.name;
} catch (err) {
// In theory we should never get here
return undefined;
Expand Down
30 changes: 30 additions & 0 deletions packages/core/src/metrics/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export const COUNTER_METRIC_TYPE = 'c' as const;
export const GAUGE_METRIC_TYPE = 'g' as const;
export const SET_METRIC_TYPE = 's' as const;
export const DISTRIBUTION_METRIC_TYPE = 'd' as const;

/**
* Normalization regex for metric names and metric tag names.
*
* This enforces that names and tag keys only contain alphanumeric characters,
* underscores, forward slashes, periods, and dashes.
*
* See: https://develop.sentry.dev/sdk/metrics/#normalization
*/
export const NAME_AND_TAG_KEY_NORMALIZATION_REGEX = /[^a-zA-Z0-9_/.-]+/g;

/**
* Normalization regex for metric tag values.
*
* This enforces that values only contain words, digits, or the following
* special characters: _:/@.{}[\]$-
*
* See: https://develop.sentry.dev/sdk/metrics/#normalization
*/
export const TAG_VALUE_NORMALIZATION_REGEX = /[^\w\d_:/@.{}[\]$-]+/g;

/**
* This does not match spec in https://develop.sentry.dev/sdk/metrics
* but was chosen to optimize for the most common case in browser environments.
*/
export const DEFAULT_FLUSH_INTERVAL = 5000;
Loading

0 comments on commit b3269ca

Please sign in to comment.