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

feat: Add axiom logger, nextjs and react libraries #257

Draft
wants to merge 39 commits into
base: main
Choose a base branch
from

Conversation

gabrielelpidio
Copy link
Collaborator

No description provided.

Copy link
Collaborator

@dasfmi dasfmi left a comment

Choose a reason for hiding this comment

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

I would rename the core package to match the name in package.json, so that it won't lead to confusion.

@gabrielelpidio
Copy link
Collaborator Author

I would rename the core package to match the name in package.json, so that it won't lead to confusion.

Changed this, now the code is under logger folder

Copy link

pkg-pr-new bot commented Jan 21, 2025

Open in Stackblitz

@axiomhq/js

npm i https://pkg.pr.new/axiomhq/axiom-js/@axiomhq/js@257

@axiomhq/logging

npm i https://pkg.pr.new/axiomhq/axiom-js/@axiomhq/logging@257

@axiomhq/pino

npm i https://pkg.pr.new/axiomhq/axiom-js/@axiomhq/pino@257

@axiomhq/react

npm i https://pkg.pr.new/axiomhq/axiom-js/@axiomhq/react@257

@axiomhq/nextjs

npm i https://pkg.pr.new/axiomhq/axiom-js/@axiomhq/nextjs@257

@axiomhq/winston

npm i https://pkg.pr.new/axiomhq/axiom-js/@axiomhq/winston@257

commit: e35921a

@@ -0,0 +1,12 @@
import { Logger } from '@axiomhq/logging';
import { AxiomProxyTransport, ConsoleTransport } from '@axiomhq/logging/transports';
Copy link
Collaborator

Choose a reason for hiding this comment

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

would it be easier if we simplify this import to:

import { Logger, AxiomProxyTransport, ConsoleTransport } from '@axiomhq/logging';

Copy link
Contributor

Choose a reason for hiding this comment

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

Don't think so, having the transports in a separate/grouped export feels like a good separation. Reminds me of how vercel does the AI sdk, you import core functionality from the "ai" package, but need to import your models from a separate package.

ex:

import { generateText } from "ai"
import { mistral } from "@ai-sdk/mistral"

const { text } = await generateText({
  model: mistral("mistral-large-latest"),
  prompt: "What is love?"
})

it has the benefit of allowing users to have a slimmer list of options in their autocomplete when importing from the main package, mainly functionalities, and have a clear place to get these things that serve a very specific purpose that you need to pass in conjunction with other functions (transports/models)

Copy link
Collaborator

Choose a reason for hiding this comment

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

@bdsqqq I agree to the concept, and that's what we are trying to do with the package, but the transports plays a core value (main funcationality) for the logging package and there is like 2 or 3 of them maximum. The Ai ones makes sense because they are different providers that would be implemented/developed separately as apposite to the transports here. wdyt?

"author": "",
"license": "ISC",
"dependencies": {
"@axiomhq/js": "^1.3.1"
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think the version here needs to be "workspace:*"

Copy link
Collaborator

Choose a reason for hiding this comment

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

@gabrielelpidio this is still mentioning a fixed version instead of workspace version.

packages/react/package.json Outdated Show resolved Hide resolved
@@ -0,0 +1,44 @@
{
Copy link
Collaborator

Choose a reason for hiding this comment

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

Make sure to go over all the package.json files in the PR and bring them closer to whats in axiom-js package.json in terms of license, contributors, homepage, repo, etc.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It should be good now; check it out @dasfmi

Copy link
Collaborator

@dasfmi dasfmi left a comment

Choose a reason for hiding this comment

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

@gabrielelpidio great work, I left some comments but they are small ones, in general looks one step closer to 100%.

@dasfmi
Copy link
Collaborator

dasfmi commented Jan 22, 2025

One more question, how do we handle the notFound and redirect errors for Next.js? We will need to address the request for setting defaults somehow.

@gabrielelpidio
Copy link
Collaborator Author

One more question, how do we handle the notFound and redirect errors for Next.js? We will need to address the request for setting defaults somehow.

This was top of mind when doing the API for route handlers. Everything is customizable, from error handling to the ingested flow; in this case, we provide a default logErrorByStatusCode function that decides which type of log level will be sent to Axiom, depending on the status code.

logger[logErrorByStatusCode(report.statusCode)](message, report);

Internally, the function does the following:

export const logErrorByStatusCode = (statusCode: number) => {
switch (statusCode) {
case 404:
case 403:
case 401:
return 'warn';
case 307:
case 308:
return 'info';
default:
return 'error';
}
};

This means developers can create their own function, with different LogLevels depending on their needs.

Deeper look (relevant to the question, but not necessary to change the LogLevel):

On the other side, capturing the status code is done internally by the getNextErrorStatusCode function, with helpers that Next.js provides to detect if it's a redirect error or a httpFallbackError

export const getNextErrorStatusCode = (error: Error & { digest?: string }) => {
if (!error.digest) {
return 500;
}
if (isRedirectError(error)) {
return error.digest.split(';')[3];
} else if (isHTTPAccessFallbackError(error)) {
return error.digest.split(';')[1];
}
};

All this also means that the function that handles errors and the default callback are just coupling these pieces together, and if the developer wants they can use as many helpers as they want from what we export

export const transformErrorResult = (data: ErrorData): [message: string, report: Record<string, any>] => {
const statusCode = data.error instanceof Error ? getNextErrorStatusCode(data.error) : 500;
const report = {
type: 'request',
method: data.req.method,
url: data.req.url,
statusCode: statusCode,
durationMs: data.end - data.start,
path: new URL(data.req.url).pathname,
endTime: data.end,
startTime: data.start,
};
return [`${data.req.method} ${report.path} ${report.statusCode} in ${report.endTime - report.startTime}ms`, report];
};

export const createDefaultAxiomHandlerCallback = (logger: Logger): axiomHandlerCallback => {
return async (result) => {
if (result.ok) {
logger.info(...transformSuccessResult(result.data));
} else {
if (result.data.error instanceof Error) {
logger.error(result.data.error.message, result.data.error);
const [message, report] = transformErrorResult(result.data);
logger[logErrorByStatusCode(report.statusCode)](message, report);
}
}
};
};

All of this lives in a single file under /packages/nextjs/src/routeHandler.ts

export type LoggerConfig = {
args?: { [key: string]: any };
transports: [Transport, ...Transport[]];
logLevel?: LogLevel;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Make sure log level works if it's gonna be included

@gabrielelpidio gabrielelpidio force-pushed the axiom-logger branch 2 times, most recently from eda0753 to 4095fc1 Compare January 27, 2025 12:54
"author": "",
"license": "ISC",
"dependencies": {
"@axiomhq/js": "^1.3.1"
Copy link
Collaborator

Choose a reason for hiding this comment

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

@gabrielelpidio this is still mentioning a fixed version instead of workspace version.

@@ -0,0 +1,14 @@
import { Transport } from '.';
import { SimpleFetchTransport } from './fetch';
interface AxiomProxyConfig {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nitpick: insert a new line before interface.

flush: () => Promise<void> | void;
}

export * from './axiom-js';
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't like how the index file is import/export other transports, and the other transports are importing the Transport interface, cycle dependencies are always weird. I would prefer if the Transport interface lived in its own file in the src folder.

"vitest": "^0.34.6"
},
"peerDependencies": {
"next": "15.1.4"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Perhaps you can be more flexible with the versioning here, something like >=15.1

"logging"
],
"dependencies": {
"@axiomhq/logging": "workspace:*"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Wondering if this should this be a peer dependency or not? No strong opinion though

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants