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

DX-196; use axiom ingest endpoints #67

Merged
merged 82 commits into from Nov 30, 2022
Merged
Show file tree
Hide file tree
Changes from 81 commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
6f837f9
DX-196; use axiom ingest endpoints
schehata Sep 19, 2022
6a107e8
ignore .envrc files
schehata Sep 19, 2022
2bc76ec
init backward compatibility for vercel
schehata Sep 19, 2022
bd0c693
fix format
schehata Sep 19, 2022
b8a6d29
properly use vercel ingest endpoint if exists
schehata Sep 19, 2022
a2d70b3
fix url construction
schehata Sep 19, 2022
3e0f9bc
fix web-vitals rewrite endpoint
schehata Sep 19, 2022
50a5e7e
clear separation of platforms
schehata Sep 19, 2022
53dbd3b
separate payload structure for web-vitals
schehata Sep 19, 2022
3a58089
make logger and webVitals agnostic to platform
schehata Sep 20, 2022
9c5a333
split vercel and generic config tests
schehata Sep 20, 2022
a8be167
for vercel, ignore platform object
schehata Sep 28, 2022
2343571
debug url,env,region
schehata Sep 28, 2022
3be04a6
debug vercel config
schehata Sep 28, 2022
672fb27
debug vercel detection
schehata Sep 28, 2022
f9b6436
fix logs url for vercel
schehata Sep 28, 2022
8e3f14c
separate netlify configurator
schehata Sep 28, 2022
ee35951
detection of netlify fails
schehata Sep 28, 2022
46d9a90
resolve env variables at build time
schehata Sep 28, 2022
2e294ee
fix misspelling
schehata Sep 28, 2022
c850675
debug axiom url for netlify
schehata Sep 28, 2022
c98b116
fix ingest url for generic config
schehata Sep 29, 2022
73e81de
debug env vars in frontend
schehata Sep 29, 2022
67d7011
debug isVercel
schehata Sep 30, 2022
3d13b25
check ingestEndpoint for vercel
schehata Sep 30, 2022
ba5971c
fix isVercel for lambda functions
schehata Oct 3, 2022
5fd906c
fix region value
schehata Oct 3, 2022
ac33182
debug isNetlify
schehata Oct 3, 2022
b124131
debug is netlify
schehata Oct 3, 2022
b21f32f
log all env vars
schehata Oct 3, 2022
07a70e3
fix edge detection on netlify
schehata Oct 3, 2022
4c95ade
debug log metadata
schehata Oct 3, 2022
4acaf83
fix netlify deployment env vars
schehata Oct 3, 2022
2771c64
fallback to NODE_ENV if VERCEL_ENV is empty
schehata Oct 4, 2022
e6d5ca5
separate platform configurators to different files
schehata Oct 4, 2022
88734c8
debug config
schehata Oct 4, 2022
b43558a
debug vercel config
schehata Oct 4, 2022
5d7798a
debug endpoints
schehata Oct 4, 2022
00d8150
debug axiom url
schehata Oct 4, 2022
d4b178e
debug ingestEndpont
schehata Oct 4, 2022
ebb7d78
fix format
schehata Oct 7, 2022
86a089e
separate config to its own files
schehata Oct 7, 2022
437d341
improve isEnvVarSet for vercel config class
schehata Nov 11, 2022
e9f9c52
debug IsEnvVarsSet
schehata Nov 11, 2022
6c2d0af
convert isEnvVarsSet to a function
schehata Nov 14, 2022
c7dcaaf
debug vercel edge function env vars
schehata Nov 15, 2022
89b3e1f
debug generic class
schehata Nov 15, 2022
ea757b8
debug config class
schehata Nov 15, 2022
c8392a7
debug process.env
schehata Nov 15, 2022
d64c3c4
debug vars
schehata Nov 15, 2022
6476fdc
fix envVarExists func
schehata Nov 15, 2022
9192ebb
remove envVarExists
schehata Nov 15, 2022
40b1c1e
debug netlify frontend
schehata Nov 15, 2022
d53e036
use beacon only with vercel
schehata Nov 15, 2022
70c7bc8
override getWebvitals endpoint for vercel
schehata Nov 15, 2022
78e1c47
debug rewritres
schehata Nov 15, 2022
26aefa0
add environment to webVitals test payload
schehata Nov 21, 2022
284d3e3
use proxy path for webVitals in vercel config class
schehata Nov 21, 2022
83cad27
fix webVitals test
schehata Nov 21, 2022
2677165
set version to v0.16.0
schehata Nov 21, 2022
7265092
debug vercel rewrites
schehata Nov 21, 2022
d3df759
remove debug logs
schehata Nov 21, 2022
1b1abe7
add return type to isEnvVarSet
schehata Nov 22, 2022
a540bbc
add return type of isEnvVarsSet
schehata Nov 22, 2022
d77081a
attach auth header only if token is provided
schehata Nov 22, 2022
53f6376
rename platformconfig interface to Provider
schehata Nov 22, 2022
967f6ce
Update src/platform/generic.ts
schehata Nov 22, 2022
8b1085a
use if/else for readability
schehata Nov 22, 2022
5f92daf
hide AXIOM_URL and provide default value
schehata Nov 23, 2022
bf46163
rename platform to netlify, remove provider info
schehata Nov 23, 2022
455cea5
rename webvitals source to web-vital for netlify
schehata Nov 23, 2022
4bc6d54
Update README.md
schehata Nov 24, 2022
7735a75
send webVitals one by one
schehata Nov 25, 2022
84621ca
debug netlify config
schehata Nov 25, 2022
e74a4d5
debug envVarsSet
schehata Nov 25, 2022
0951cd5
fix generic class isEnvVarsSet
schehata Nov 25, 2022
48e9ea5
clean debugging logs
schehata Nov 25, 2022
67b7403
Merge branch 'main' into without-vercel
schehata Nov 25, 2022
ec5ecc7
add netlify? to LogEvent interface
schehata Nov 28, 2022
8f7a2b5
Merge branch 'main' into without-vercel
schehata Nov 28, 2022
cdbb71d
Merge branch 'main' into without-vercel
schehata Nov 29, 2022
49837a4
Merge branch 'main' into without-vercel
schehata Nov 30, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -5,3 +5,4 @@
/src/*.js.map
/dist/
/.vscode
.envrc
18 changes: 12 additions & 6 deletions README.md
Expand Up @@ -17,17 +17,23 @@

For more information check out the [official documentation](https://axiom.co/docs).

## Usage
## Quickstart

First of all make sure you have the [Axiom Vercel integration](https://www.axiom.co/vercel)
installed.
Then in your Next.js project, run install `next-axiom` like this:
- If you are using Vercel, make sure you have the [Axiom Vercel integration](https://www.axiom.co/vercel)
installed. On other platforms you must create an API token and set those environment variables:

```
AXIOM_DATASET: the dataset the logs will be ingested into
AXIOM_TOKEN: the API token you created for ingestion to the dataset
```

- Then in your Next.js project, run install `next-axiom` like this:

```sh
npm install --save next-axiom
```

Wrap your Next.js config in `withAxiom` like this in `next.config.js`:
- Wrap your Next.js config in `withAxiom` like this in `next.config.js`:

```js
const { withAxiom } = require('next-axiom')
Expand All @@ -37,7 +43,7 @@ module.exports = withAxiom({
})
```

Go to `pages/_app.js` or `pages/_app.ts` and add the following line to report web vitals:
- Go to `pages/_app.js` or `pages/_app.ts` and add the following line to report web vitals:

```js
export { reportWebVitals } from 'next-axiom'
Expand Down
14 changes: 14 additions & 0 deletions __tests__/genericConfig.vercel.ts
@@ -0,0 +1,14 @@
process.env.AXIOM_INGEST_ENDPOINT = '';
process.env.AXIOM_URL = 'https://test.axiom.co';
process.env.AXIOM_DATASET = 'test';

import config from '../src/config';
import { EndpointType } from '../src/shared';

test('reading axiom ingest endpoint', () => {
let url = config.getIngestURL(EndpointType.webVitals);
expect(url).toEqual('https://test.axiom.co/api/v1/datasets/test/ingest');

url = config.getIngestURL(EndpointType.logs);
expect(url).toEqual('https://test.axiom.co/api/v1/datasets/test/ingest');
});
3 changes: 3 additions & 0 deletions __tests__/noEnvVars.test.ts
Expand Up @@ -3,6 +3,9 @@
*/

// clear Axiom env vars
process.env.AXIOM_URL = '';
process.env.AXIOM_DATASET = '';
process.env.AXIOM_TOKEN = '';
process.env.AXIOM_INGEST_ENDPOINT = '';
process.env.NEXT_PUBLIC_AXIOM_INGEST_ENDPOINT = '';
import { NextWebVitalsMetric } from 'next/app';
Expand Down
10 changes: 0 additions & 10 deletions __tests__/shared.test.ts

This file was deleted.

13 changes: 13 additions & 0 deletions __tests__/vercelConfig.test.ts
@@ -0,0 +1,13 @@
process.env.AXIOM_URL = undefined;
process.env.AXIOM_INGEST_ENDPOINT = 'https://axiom.co/api/v1/integrations/vercel';

import config from '../src/config';
import { EndpointType } from '../src/shared';

test('reading vercel ingest endpoint', () => {
let url = config.getIngestURL(EndpointType.webVitals);
expect(url).toEqual('https://axiom.co/api/v1/integrations/vercel?type=web-vitals');

url = config.getIngestURL(EndpointType.logs);
expect(url).toEqual('https://axiom.co/api/v1/integrations/vercel?type=logs');
});
13 changes: 11 additions & 2 deletions __tests__/webvitals.test.ts
Expand Up @@ -4,7 +4,8 @@
import { jest } from '@jest/globals';
import { NextWebVitalsMetric } from 'next/app';
// set axiom env vars before importing webvitals
process.env.AXIOM_INGEST_ENDPOINT = 'https://example.co/api/test';
process.env.AXIOM_URL = '';
process.env.NEXT_PUBLIC_AXIOM_INGEST_ENDPOINT = 'https://example.co/api/test';
import { reportWebVitals } from '../src/webVitals';
import 'whatwg-fetch';

Expand Down Expand Up @@ -37,18 +38,26 @@ test('throttled sendMetrics', async () => {
jest.advanceTimersByTime(1000);

const url = '/_axiom/web-vitals';
const payload = { method: 'POST', keepalive: true };
const payload = {
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
keepalive: true,
};

expect(fetch).toHaveBeenCalledTimes(2);
expect(fetch).nthCalledWith(1, url, {
body: JSON.stringify({
webVitals: [...metricsMatrix[0], ...metricsMatrix[1]],
environment: 'test',
}),
...payload,
});
expect(fetch).nthCalledWith(2, url, {
body: JSON.stringify({
webVitals: metricsMatrix[2],
environment: 'test',
}),
...payload,
});
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
@@ -1,7 +1,7 @@
{
"name": "next-axiom",
"description": "Send WebVitals from your Next.js project to Axiom.",
"version": "0.15.1",
"version": "0.16.0",
"author": "Axiom, Inc.",
"license": "MIT",
"contributors": [
Expand Down
17 changes: 17 additions & 0 deletions src/config.ts
@@ -0,0 +1,17 @@
import GenericConfig from './platform/generic';
import VercelConfig from './platform/vercel';
import NetlifyConfig from './platform/netlify';

export const isVercel = process.env.AXIOM_INGEST_ENDPOINT || process.env.NEXT_PUBLIC_AXIOM_INGEST_ENDPOINT;
export const isNetlify = process.env.NETLIFY == 'true';

// Detect the platform provider, and return the appropriate config
// fallback to generic config if no provider is detected
let config = new GenericConfig();
if (isVercel) {
config = new VercelConfig();
} else if (isNetlify) {
config = new NetlifyConfig();
}

export default config;
57 changes: 29 additions & 28 deletions src/logger.ts
@@ -1,24 +1,18 @@
import {
proxyPath,
isBrowser,
EndpointType,
getIngestURL,
isEnvVarsSet,
isNoPrettyPrint,
vercelEnv,
vercelRegion,
} from './shared';
import { throttle } from './shared';

const url = isBrowser ? `${proxyPath}/logs` : getIngestURL(EndpointType.logs);

interface LogEvent {
import config, { isVercel } from './config';
import { NetlifyInfo } from './platform/netlify';
import { isNoPrettyPrint, throttle } from './shared';

const url = config.getLogsEndpoint();

export interface LogEvent {
level: string;
message: string;
fields: {};
_time: string;
request?: RequestReport;
vercel?: VercelData;
platform?: PlatformInfo;
vercel?: PlatformInfo;
bahlo marked this conversation as resolved.
Show resolved Hide resolved
netlify?: NetlifyInfo;
}

export interface RequestReport {
Expand All @@ -33,7 +27,7 @@ export interface RequestReport {
userAgent?: string | null;
}

interface VercelData {
export interface PlatformInfo {
environment?: string;
region?: string;
route?: string;
Expand Down Expand Up @@ -84,15 +78,15 @@ export class Logger {
logEvent.fields = { ...logEvent.fields, args: args };
}

logEvent.vercel = {
environment: vercelEnv,
region: vercelRegion,
source: this.source,
};
config.injectPlatformMetadata(logEvent, this.source);

if (this.req != null) {
logEvent.request = this.req;
logEvent.vercel.route = this.req.path;
if (logEvent.platform) {
logEvent.platform.route = this.req.path;
} else if (logEvent.vercel) {
logEvent.vercel.route = this.req.path;
}
}

this.logEvents.push(logEvent);
Expand All @@ -115,7 +109,7 @@ export class Logger {
return;
}

if (!isEnvVarsSet) {
if (!config.isEnvVarsSet()) {
// if AXIOM ingesting url is not set, fallback to printing to console
// to avoid network errors in development environments
this.logEvents.forEach((ev) => prettyPrint(ev));
Expand All @@ -128,15 +122,22 @@ export class Logger {
const body = JSON.stringify(this.logEvents);
// clear pending logs
this.logEvents = [];
const headers = {
'content-type': 'application/json',
};
if (config.token) {
headers['Authorization'] = `Bearer ${config.token}`;
}
const reqOptions: RequestInit = { body, method, keepalive, headers };

try {
if (typeof fetch === 'undefined') {
const fetch = await require('whatwg-fetch');
await fetch(url, { body, method, keepalive });
} else if (isBrowser && navigator.sendBeacon) {
await fetch(url, reqOptions);
} else if (config.isBrowser && isVercel && navigator.sendBeacon) {
navigator.sendBeacon(url, body);
} else {
await fetch(url, { body, method, keepalive });
await fetch(url, reqOptions);
}
} catch (e) {
console.error(`Failed to send logs to Axiom: ${e}`);
Expand Down Expand Up @@ -187,7 +188,7 @@ export function prettyPrint(ev: LogEvent) {
let msgString = '';
let args: any[] = [ev.level, ev.message];

if (isBrowser) {
if (config.isBrowser) {
msgString = '%c%s - %s';
args = [`color: ${levelColors[ev.level].browser};`, ...args];
} else {
Expand Down
22 changes: 22 additions & 0 deletions src/platform/base.ts
@@ -0,0 +1,22 @@
import { NextWebVitalsMetric } from 'next/app';
import { RequestReport } from '../logger';
import { EndpointType } from '../shared';

// This is the base class for all platform providers. It contains all the different
// configrations per provider, and the functions that are used by the logger. Implement
// this interface to have special behaviour for your platform.
export default interface Provider {
shoudSendEdgeReport: boolean;
token: string | undefined;
environment: string;
region: string | undefined;
axiomUrl: string | undefined;

isEnvVarsSet(): boolean;
getIngestURL(t: EndpointType): string;
wrapWebVitalsObject(metrics: NextWebVitalsMetric[]): any;
injectPlatformMetadata(logEvent: any, source: string): void;
generateRequestMeta(req: any): RequestReport;
getLogsEndpoint(): string
getWebVitalsEndpoint(): string
}
69 changes: 69 additions & 0 deletions src/platform/generic.ts
@@ -0,0 +1,69 @@
import { NextApiRequest } from "next";
import { LogEvent, RequestReport } from "../logger";
import { EndpointType } from "../shared";
import type Provider from "./base";

// This is the generic config class for all platforms that doesn't have a special
// implementation (e.g: vercel, netlify). All config classes extends this one.
export default class GenericConfig implements Provider {
proxyPath = '/_axiom';
isBrowser = typeof window !== 'undefined';
shoudSendEdgeReport = false;
token = process.env.AXIOM_TOKEN;
dataset = process.env.AXIOM_DATASET;
environment: string = process.env.NODE_ENV;
axiomUrl = process.env.AXIOM_URL || 'https://cloud.axiom.co';
region = process.env.REGION || undefined;

isEnvVarsSet(): boolean {
return !!(this.axiomUrl && process.env.AXIOM_DATASET && process.env.AXIOM_TOKEN);
}

getIngestURL(_: EndpointType): string {
return `${this.axiomUrl}/api/v1/datasets/${this.dataset}/ingest`;
}

getLogsEndpoint(): string {
return this.isBrowser ? `${this.proxyPath}/logs` : this.getIngestURL(EndpointType.logs);
}

getWebVitalsEndpoint(): string {
return this.isBrowser ? `${this.proxyPath}/logs` : this.getIngestURL(EndpointType.webVitals);
}

wrapWebVitalsObject(metrics: any[]): any {
return metrics.map(m => ({
webVital: m,
_time: new Date().getTime(),
platform: {
environment: this.environment,
source: 'web-vital',
},
}))
}

injectPlatformMetadata(logEvent: LogEvent, source: string) {
logEvent.platform = {
environment: this.environment,
region: this.environment,
source: source,
};
}

generateRequestMeta(req: any): RequestReport {
return {
startTime: new Date().getTime(),
path: req.url!,
method: req.method!,
host: this.getHeaderOrDefault(req, 'host', ''),
userAgent: this.getHeaderOrDefault(req, 'user-agent', ''),
scheme: 'https',
ip: this.getHeaderOrDefault(req, 'x-forwarded-for', ''),
region: this.region,
};
}

getHeaderOrDefault(req: NextApiRequest, headerName: string, defaultValue: any) {
return req.headers[headerName] ? req.headers[headerName] : defaultValue;
}
}