Skip to content

Commit

Permalink
Merge pull request #15 from legowerewolf/legowerewolf/issue9
Browse files Browse the repository at this point in the history
Legowerewolf/issue9
  • Loading branch information
legowerewolf authored Nov 1, 2020
2 parents bccc5c9 + a038df8 commit 1a0c355
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 5 deletions.
63 changes: 63 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,69 @@
[Deno](https://deno.land/) implementation of
[Ratlog log formatter](https://github.com/ratlog/ratlog-spec).

## Getting started

This package exposes two APIs. The more fully-featured of the two tries to match
the [Ratlog.js](https://github.com/ratlog/ratlog.js) API, and is henceforth
called the Classic API.

```ts
// Import the Classic API from deno.land/x/
import ratlog from "https://deno.land/x/[email protected]/classic-api.ts";

// Set up logging through the console output
const log = ratlog(console.log);

log("hello, world");
//> hello, world

// Add fields
log("counting", { count: 1 });
//> counting | count: 1

// Add fields and a tag
log("counting", { count: -1 }, "negative");
//> [negative] counting | count: -1

// Create another logger bound to a tag
const warn = log.tag("warning");

warn("disk space low");
//> [warning] disk space low

// Combine and nest tags any way you like
const critical = warn.tag("critical");

critical("shutting down all servers");
//> [warning|critical] shutting down all servers
```

The core of the implementation is exposed in `./ratlog.ts`, and is called the
Core API. It's less fully featured, but provides a parsing implementation.

```ts
// Import the Core API from deno.land/x/
import Ratlog from "https://deno.land/x/[email protected]/ratlog.ts";

Ratlog.log({ message: "hello, world" });
// returns "hello, world"

Ratlog.log({ message: "counting", fields: { count: 1 } });
// returns "counting | count: 1"

Ratlog.log({ message: "counting", tags: ["negative"], fields: { count: -1 } });
// returns "[negative] counting | count: -1"

Ratlog.parse("[negative] counting | count: -1");
// returns { message: "counting", tags: ["negative"], fields: { count: -1 } }

Ratlog.parse("counting | count: 1");
// returns { message: "counting", fields: { count: 1 } }

Ratlog.parse("hello, world");
// returns { message: "hello, world" }
```

## How to help

- Check out
Expand Down
42 changes: 39 additions & 3 deletions classic-api.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,44 @@
import Ratlog, { RatlogData, Stringable } from "./ratlog.ts";

/**
* Interface representing a stream or function passed to the ratlogger constructor.
*
* It's either itself a function that takes data and returns nothing, or an object
* with a `write()` function that takes data and returns nothing.
*/
type Writer<T> = ((data: T) => void) | { write: (data: T) => void };

/**
* Identify the single function to be called when outputting to a writer
* @param writer writer to identify output function for
*/
const getWriteFunc = <T>(writer: Writer<T>) =>
"write" in writer ? writer.write.bind(writer) : writer;

/**
* Take a function and an object and produce a function with properties.
* @param func the function to attach properties to
* @param obj an object containing properties to copy to the function
*/
const enrichFunction = <F extends Function, O>(func: F, obj: O): F & O =>
Object.assign(func, obj);

type Ratlogger = ((
/**
* A `Ratlogger` is a function that takes the components of a `RatlogData` and
* does something with it. It also exposes a `tag` property-function which produces
* an identical `Ratlogger` with the added tags.
*/
export type Ratlogger = ((
message: Stringable,
fields?: RatlogData["fields"],
...tags: Stringable[]
) => void) & { tag: (...tags: Stringable[]) => Ratlogger };

/**
* Constructor for more customizable logger instances.
* @param writer a writable stream or function that can take RatlogData
* @param tags a list of tags to apply to every output from this logger
*/
const generateRatlogger = (
writer: Writer<RatlogData>,
...tags: Stringable[]
Expand All @@ -32,21 +58,31 @@ const generateRatlogger = (
});
},
{
tag: (...tags: Stringable[]): Ratlogger =>
generateRatlogger(writer, ...originalTags.concat(tags)),
tag: (
/** a list of tags to apply to every output from this logger */
...tags: Stringable[]
): Ratlogger => generateRatlogger(writer, ...originalTags.concat(tags)),
}
);
};

const ratlog = (() => {
return enrichFunction(
/**
* @param writer a writable stream or function
* @param tags a list of tags to apply to every output from this logger
* @returns a logging function bound to the writer
*/
(writer: Writer<string>, ...tags: Stringable[]) =>
generateRatlogger(
(data: RatlogData) => getWriteFunc(writer)(Ratlog.format(data)),
...tags
),
{
/** Constructor for more customizable logger instances. */
logger: generateRatlogger,

/** Exposure of the core Ratlog string formatter. */
stringify: Ratlog.format,
}
);
Expand Down
19 changes: 19 additions & 0 deletions ratlog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,27 @@ import {
unescapeTag,
} from "./stringmanip.ts";

/**
* An object that either is a string, or reveals a toString() function.
*
* Utility type, used to make the Ratlog API more flexible.
*/
export type Stringable = string | { toString: () => string };

/**
* The base Ratlog data type. All logs are serialized from and parsed to objects of this type.
*/
export interface RatlogData {
message: Stringable;
tags?: Stringable[];
fields?: Record<string, Stringable | null>;
}

export default class Ratlog {
/**
* Take a 'line' of Ratlog data and format it for output.
* @param data the log line to format
*/
static format(data: RatlogData): string {
let tagString =
data.tags?.length ?? 0 > 0
Expand All @@ -40,6 +52,13 @@ export default class Ratlog {
return escape("\n")(tagString + messageString + fieldString) + "\n";
}

/**
* Take a string and parse it.
*
* Known to work on standards-compliant Ratlog formatter output. Not guaranteed to work with log data that doesn't meet the spec.
*
* @param logline a line of text to parse as Ratlog data
*/
static parse(logline: string): RatlogData {
let data: Partial<RatlogData> = {};

Expand Down
31 changes: 29 additions & 2 deletions stringmanip.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
/**
* Transform a string with the given functions
* @param str a string to be transformed
* @param mut an array of string transformation functions to be applied left-to-right to `str`
*/
export const apply = (str: string, mut: ((str: string) => string)[]) =>
mut.reduce((prev, func) => func(prev), str);

export const escape = (symbol: string) => (input: string) => {
/**
* Generate a function that escapes a given character for input strings
* @param symbol a symbol (character) to generate an escape function for
*/
export const escape = (symbol: string) => (
/** A string to escape */ input: string
) => {
switch (symbol) {
case "\n":
return input.replaceAll("\n", `\\n`);
Expand All @@ -10,7 +21,13 @@ export const escape = (symbol: string) => (input: string) => {
}
};

export const unescape = (symbol: string) => (input: string) => {
/**
* Generate a function that unescapes a given character for input strings
* @param symbol a symbol (character) to generate an unescape function for
*/
export const unescape = (symbol: string) => (
/** A string to unescape */ input: string
) => {
switch (symbol) {
case "\n":
return input.replaceAll(`\\n`, `\n`);
Expand All @@ -19,16 +36,26 @@ export const unescape = (symbol: string) => (input: string) => {
}
};

/** Escape symbols unsafe for tags */
export const escapeTag = (tag: string) =>
apply(tag, [escape("]"), escape("|")]);

/** Escape symbols unsafe for messages */
export const escapeMessage = (message: string) =>
apply(message, [escape("["), escape("|")]);

/** Escape symbols unsafe for fields */
export const escapeField = (fieldval: string) =>
apply(fieldval, [escape(":"), escape("|")]);

/** Unescape symbols unsafe for tags */
export const unescapeTag = (tag: string) =>
apply(tag, [unescape("]"), unescape("|")]);

/** Unescape symbols unsafe for messages */
export const unescapeMessage = (message: string) =>
apply(message, [unescape("["), unescape("|")]);

/** Unescape symbols unsafe for fields */
export const unescapeField = (fieldpart: string) =>
apply(fieldpart, [unescape(":"), unescape("|")]);

0 comments on commit 1a0c355

Please sign in to comment.