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

improve extensibility of logtape #6

Closed
wants to merge 5 commits into from
Closed

Conversation

okikio
Copy link

@okikio okikio commented Aug 14, 2024

Improve the extensibility of logtape.

  1. Currently logtape has a number of exported functions that are are useful and necessary to build on top of logtape but aren't accessible, this pr adds all the missing modules to package exports for both dnt and jsr, this should make it easier to build on top of logtape.
  2. The logTemplate and logLazily methods are limited in odd ways compared to the log method which makes it difficult to extend logtape and create custom log functions, e.g. group, groupCollapsed, dir, dirxml, timeStart, timeEnd, table, etc... by adding the properties parameter to both methods we can now store a groupId in properties consistently, or maybe even store that the expected output is a table, etc...
  3. The addition of the bypassSink parameter is for just in case situations, if the log method can use bypassSink I am not sure why logTemplate and logLazily shouldn't be able to do the same, I can always remove the parameter if people feel it isn't a good fit.

okikio added 2 commits August 14, 2024 03:46
Signed-off-by: Okiki <hey@okikio.dev>
…mplate

Signed-off-by: Okiki <hey@okikio.dev>
Copy link

codecov bot commented Aug 14, 2024

Codecov Report

Attention: Patch coverage is 40.00000% with 18 lines in your changes missing coverage. Please review.

Project coverage is 93.94%. Comparing base (efce6f3) to head (0cdf993).

Files Patch % Lines
logtape/logger.ts 40.00% 18 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main       #6      +/-   ##
==========================================
- Coverage   96.23%   93.94%   -2.30%     
==========================================
  Files           9        9              
  Lines         717      743      +26     
  Branches      132      134       +2     
==========================================
+ Hits          690      698       +8     
- Misses         26       44      +18     
  Partials        1        1              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Owner

@dahlia dahlia left a comment

Choose a reason for hiding this comment

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

Thanks for your first contribution! I left few comments! Plus, could you add entries to the changelog?

deno.json Outdated
Comment on lines 4 to 15
"exports": "./logtape/mod.ts",
"exports": {
".": "./logtape/mod.ts",
"./config": "./logtape/config.ts",
"./filesink": "./logtape/filesink.jsr.ts",
"./filter": "./logtape/filter.ts",
"./formatter": "./logtape/formatter.ts",
"./fs": "./logtape/fs.ts",
"./level": "./logtape/level.ts",
"./logger": "./logtape/logger.ts",
"./record": "./logtape/record.ts",
"./sink": "./logtape/sink.ts"
},
Copy link
Owner

Choose a reason for hiding this comment

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

The current exports are very carefully curated by me. The APIs that are not exported are not exposed for a reason. Are there any APIs that you would like to see exposed?

Copy link
Author

@okikio okikio Aug 14, 2024

Choose a reason for hiding this comment

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

For some background, I'm basically trying to wrap the logtape loggers in a abstract logger class, the goal is basically to be able to register the various loggers that someone wants to use, and to be able to use them as such,

https://gist.github.com/okikio/dab52147d880773a187f48de2b2da689

// Interface to extend Logger with custom loggers
export type LoggerWithCustomLoggers<L extends string> = Logger<L> & {
  [P in L]: Logger<L>;
}

// Function to create a logger with typed custom loggers
export function createLogger<L extends string>(options: LoggerOptions<L> = {}): LoggerWithCustomLoggers<L> {
  return new Logger(options) as LoggerWithCustomLoggers<L>;
}

export const initLogger = getLogger(["@bundle/core", "init"]);
export const buildLogger = getLogger(["@bundle/core", "build"]);
export const generalLogger = getLogger(["@bundle/core", "general"]);

const logger = createLogger({
  loggers: {
    init: initLogger,
    build: buildLogger,
    customLogger: getLogger(["@bundle/core", "custom"]),
  },
});

// Logs to the general logger by default
logger.info`General Log ${5}`;

// Logs to the init logger with a custom template
logger.with({ logger: "init", message: "start {time}" }).warn({ time: Date.now() });

// Logs an error to the init logger
logger.with({ logger: "init" }).error("error {message}", { message: "Error message" });

// Logs an error to the init logger (with context)
logger.with({ logger: "init" }).context({ message: "Error message" }).error("error {message}");

// Logs an error to the init logger (with context no. 2)
logger.with({ logger: "init", context: { message: "Error message" } }).error("error {message}");

// Shortcut for init logger
logger.init.error("Initialization failed: {error}", { error: new Error("cool") });

// Logs to the custom logger
logger.customLogger.info("Custom logger in action");

// Shortcut for build logger
logger.build.fatal`A fatally cool message`;

// This will now cause a TypeScript error, as 'nonExistentLogger' is not registered
// logger.nonExistentLogger.info("This should not compile");

To achieve this workflow I need to add context to the logger, but that get's complicated as the original logtape Logger class doesn't really support the concept of contextual logs (though it is possible to replicate).

In addition, logtape has a couple of unhandled edge cases that context can help with, e.g. what happens if the user wants to log out a variable that ends up being a function, etc... For this to work I need to be able to fully control how log records are emitted, that's the main reason for needing all the modules to be exported in the exports field.

Copy link
Author

Choose a reason for hiding this comment

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

The API's I would need access to are basically all types and interfaces, the LoggerImpl class, the log, logLazily, logTemplate & emit methods, and parseMessageTemplate & renderMessage functions.

Copy link
Owner

Choose a reason for hiding this comment

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

Looking at your gist you linked to, I still don't understand how LoggerImpl and its methods are used. 🤔

Basically, I don't want to expose things like LoggerImpl because the wider the API surface, the harder it is to change the internal implementation without breaking backwards compatibility.

However, if you have a feature that you need in LogTape, it may be better to modify LogTape itself rather than extend it externally. I'm not sure what exactly the context you're trying to implement is for though. 😅

Copy link
Author

Choose a reason for hiding this comment

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

For LoggerImpl if possible exporting it as a type would potentially solve that issue. But I would still need access to the parseMessageTemplate & renderMessage functions. Not sure what can be done about that.

Copy link
Author

Choose a reason for hiding this comment

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

I've made a couple changes to the pr, I've created a new types.ts module where I just export all the types instead of exporting all the code, I then export parseMessageTemplate and renderMessage in the mod.ts file, what do you think?

dnt.ts Outdated
Comment on lines 29 to 40
entryPoints: ["./logtape/mod.ts"],
entryPoints: [
"./logtape/mod.ts",
{ name: "./config", path: "./logtape/config.ts" },
{ name: "./filesink", path: "./logtape/filesink.node.ts" },
{ name: "./filter", path: "./logtape/filter.ts" },
{ name: "./formatter", path: "./logtape/formatter.ts" },
{ name: "./fs", path: "./logtape/fs.js" },
{ name: "./level", path: "./logtape/level.ts" },
{ name: "./logger", path: "./logtape/logger.ts" },
{ name: "./record", path: "./logtape/record.ts" },
{ name: "./sink", path: "./logtape/sink.ts" }
],
Copy link
Owner

Choose a reason for hiding this comment

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

The current exports are very carefully curated by me. The APIs that are not exported are not exposed for a reason. Are there any APIs that you would like to see exposed?

okikio added 2 commits August 14, 2024 07:24
Signed-off-by: Okiki <hey@okikio.dev>
Signed-off-by: Okiki <hey@okikio.dev>
@okikio
Copy link
Author

okikio commented Aug 14, 2024

I've updated the changelogs 👍🏽

Signed-off-by: Okiki <hey@okikio.dev>
Copy link
Owner

@dahlia dahlia left a comment

Choose a reason for hiding this comment

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

I left some comments.

I still don't quite understand the purpose of the feature called context that you want to implement, but I guess it's one thing to implement it directly into LogTape if you need to. What do you think of this direction?

export type * from "./level.ts";
export type * from "./logger.ts";
export type * from "./record.ts";
export type * as Sink from "./sink.ts";
Copy link
Owner

Choose a reason for hiding this comment

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

Why was this the only one aliased?

Copy link
Author

Choose a reason for hiding this comment

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

There is a type conflict with getFileSink in ./sink.ts and ./filesink.jsr.ts, I wasn't sure how to approach the conflict.

Comment on lines +562 to +572
Object.assign(
record,
typeof properties === "function"
? {
get properties() {
if (cachedProps == null) cachedProps = properties();
return cachedProps;
},
}
: { properties },
);
Copy link
Owner

Choose a reason for hiding this comment

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

According to Codecov, this code is not covered by tests, could you please add a test to cover this part?

Comment on lines +533 to +543
Object.assign(
record,
typeof properties === "function"
? {
get properties() {
if (cachedProps == null) cachedProps = properties();
return cachedProps;
},
}
: { properties },
);
Copy link
Owner

Choose a reason for hiding this comment

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

According to Codecov, this code is not covered by tests, could you please add a test to cover this part?

Comment on lines +1 to +8
export type * from "./config.ts";
export type * from "./filesink.jsr.ts";
export type * from "./filter.ts";
export type * from "./formatter.ts";
export type * from "./level.ts";
export type * from "./logger.ts";
export type * from "./record.ts";
export type * as Sink from "./sink.ts";
Copy link
Owner

Choose a reason for hiding this comment

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

Do you need all of these types? 😅

Copy link
Author

@okikio okikio Aug 15, 2024

Choose a reason for hiding this comment

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

For extensibility purposes yes, all the types are required, however, if I'm building in context into logtape then the need decreases a bit, but other developers extending on logtape might still need them.

@okikio
Copy link
Author

okikio commented Aug 15, 2024

The main reason why I didn't is that I didn't want change the direction of project, but if you're up for I'd love to create a pr that adds support for context.

@dahlia
Copy link
Owner

dahlia commented Aug 16, 2024

@okikio I apologize for making you work multiple times, but if you don't mind, I'd like to close this PR, and could you open an issue for the concept of context instead? 😅 We can talk about the design a bit and then you can get to work on it, or I can work on it.

@okikio
Copy link
Author

okikio commented Aug 16, 2024

Sure, I'll close this pr in the meantime then

@okikio okikio closed this Aug 16, 2024
@okikio
Copy link
Author

okikio commented Aug 17, 2024

@dahlia I've opened a new issue #7, we can continue the conversation there.

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.

2 participants