├── .babelrc
├── .gitignore
├── .npmignore
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── example
├── .gitignore
├── .vscode
│ └── settings.json
├── App.tsx
├── README.md
├── babel.config.js
├── package.json
├── tsconfig.json
└── yarn.lock
├── package.json
├── src
├── constants
│ ├── index.ts
│ └── loglevel.ts
├── contexts
│ ├── LogLevelContext.ts
│ └── index.ts
├── hooks
│ ├── index.ts
│ ├── useLogLevelContext.ts
│ └── useLogs.ts
├── index.ts
├── middleware
│ ├── LogLevel.ts
│ └── index.ts
└── providers
│ ├── LogLevelProvider.tsx
│ └── index.ts
├── tsconfig.json
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-typescript", "@babel/preset-env", "@babel/preset-react"],
3 | "plugins": ["@babel/plugin-transform-typescript"]
4 | }
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | example/
2 | src/
3 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "loglevel"
4 | ]
5 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Alexander Thomas
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-use-logs
2 | ⚛️ 🖨️ A hierarchy sensitive, middleware-defined `console.log` for [**React**](https://reactjs.org) and [**React Native**](https://reactnative.dev). ✨
3 |
4 | ### Table of Contents
5 | - [**1.0 Getting Started**](#-getting-started)
6 | - [**2.0 Usage**](#%EF%B8%8F-usage)
7 | - [2.1 Basic Example](#-21-basic-example)
8 | - [2.2 Middleware](#-22-middleware)
9 | - [2.2.1 Nested Middleware](#-221-nested-middleware)
10 | - [2.3 Custom Types](#-23-custom-types)
11 | - [2.4 Filtering Logs](#24--filtering-logs)
12 | - [2.4.1 Strict Mode](#-241-strict-mode)
13 | - [**3.0 License**](#%EF%B8%8F-license)
14 |
15 |
16 | ### 🔥 Features
17 | - 🔋 Batteries included.
18 | - 🦄 Declare custom log types.
19 | - 👪 Supports JSX-scoped filtering and functionality.
20 |
21 | ## 🚀 Getting Started
22 |
23 | Using [**Yarn**](https://yarnpkg.com):
24 |
25 | ```sh
26 | yarn add react-use-logs
27 | ```
28 |
29 | ## ✏️ Usage
30 |
31 | ### 👶 2.1 Basic Example
32 | By default, `react-use-logs` exports a [`useLogs()`](./src/hooks/useLogs.ts) [**hook**](https://reactjs.org/docs/hooks-intro.html), which works pretty much just like your standard `window.console` object:
33 |
34 | ```javascript
35 | import { useLogs } from "react-use-logs";
36 |
37 | export default () => {
38 | const logs = useLogs();
39 | return
logs.debug("Normal", "Old", { console: true })} />
40 | };
41 | ```
42 |
43 | ### 🏹 2.2 Middleware
44 | It's possible to define custom handlers for particular calls to `useLogs()`. First, wrap your application in a [`
`](./src/providers/LogLevelProvider):
45 |
46 | ```javascript
47 | import React from 'react';
48 | import LogsProvider from 'react-use-logs';
49 |
50 | export default () => (
51 |
52 | {/* TODO: awesome app here */}
53 |
54 | );
55 | ```
56 |
57 | Once this is done, you can pass a custom `middleware` prop, which is an array that accepts [**Middleware**](./src/contexts/LogLevelContext.ts) functions which take the following form:
58 |
59 | ```javascript
60 | function someMiddleware(level: string, messages: unknown[], next: () => void) {
61 | // Do something with the message.
62 | alert(...messages);
63 | // Allow the next middleware in the chain to execute:
64 | next();
65 | }
66 | ```
67 |
68 | Next, you can use the middleware as follows:
69 |
70 | ```javascript
71 | import React from 'react';
72 | import LogsProvider from 'react-use-logs';
73 |
74 | import { someMiddleware, someOtherMiddleware } from './middleware';
75 |
76 | export default () => (
77 |
78 | {/* TODO: awesome app here */}
79 |
80 | );
81 | ```
82 | At this point any call to `logs.debug`, for example, will instead get passed through the middleware chain.
83 |
84 | > **Notice**: By default, a `LogsProvider` will use the default builtin logging mechanism, the `LogLevel` middleware. This is based on [`loglevel`](https://github.com/pimterry/loglevel). However, if you override the `middleware` prop for a root-level `LogsProvider`, this will _not_ be included by default. You can reintroduce the standard console behaviour by including the `LogLevel` middleware exported by the library.
85 |
86 | #### 👪 2.2.1 Nested Middleware
87 |
88 | It's also possible to declare specific middleware for different parts of the DOM tree. This is achieved by nesting a child `LogsProvider`, and declaring an additional `middleware` prop. The middleware supplied here will be _appended_ to the global middleware.
89 |
90 | ```javascript
91 | import React from 'react';
92 | import LogsProvider, { LogLevel } from 'react-use-logs';
93 |
94 | import { someMiddleware, someOtherMiddleware } from './middleware';
95 |
96 | export default () => (
97 |
98 |
99 | {/* TODO: some special logging route here */}
100 |
101 | {/* TODO: awesome app here */}
102 |
103 | );
104 | ```
105 |
106 | ### 🦄 2.3 Custom Types
107 | By default, `react-use-logs` uses the existing [window.console](https://developer.mozilla.org/en-US/docs/Web/API/Window/console) export format, i.e:
108 |
109 | ```javascript
110 | import { useLogs } from "react-use-logs";
111 |
112 | const logs = useLogs();
113 | logs.trace("");
114 | logs.debug("");
115 | logs.info("");
116 | logs.warn("");
117 | logs.error("");
118 | ```
119 |
120 | By using a custom [`LogsProvider`](./src/providers/LogLevelProvider.ts), you can specify an additional `levels` prop to declare custom log levels:
121 |
122 | ```javascript
123 | import React from 'react';
124 | import LogsProvider from 'react-use-logs';
125 |
126 | export default () => (
127 |
128 | {/* TODO: awesome app here */}
129 |
130 | );
131 | ```
132 |
133 | This will result in `useLogs` returning an object like so:
134 |
135 | ```javascript
136 | import { useLogs } from "react-use-logs";
137 |
138 | const logs = useLogs();
139 | logs.good("");
140 | logs.bad("");
141 | logs.ugly("");
142 | ```
143 |
144 | > **Notice** Similar to `middleware`, for a root-level `LogsProvider` a defined `levels` prop will override the original default levels. For nested providers, the contents of the `levels` will be _appended_ to the inherited ones.
145 |
146 | ### 2.4 🤫 Filtering Logs
147 |
148 | You can specify a `level` prop to a `LogsProvider` to declare the minimum actionable level, which obeys prioritized order. In the example below, it is only possible for `warn` and `error` events to be executed; all other invocations will be _ignored_.
149 |
150 | ```javascript
151 | import React from 'react';
152 | import LogsProvider from 'react-use-logs';
153 |
154 | export default () => (
155 |
156 | {/* TODO: silent app here */}
157 |
158 | );
159 | ```
160 |
161 | In addition, you can disable logging altogether for any child component by passing a `disabled` prop:
162 |
163 | ```javascript
164 | import React from 'react';
165 | import LogsProvider from 'react-use-logs';
166 |
167 | export default () => (
168 |
169 | {/* TODO: silent app here */}
170 |
171 | );
172 | ```
173 |
174 | #### 💢 2.4.1 Strict Mode
175 |
176 | By default, `LogsProvider`s operate in **Strict Mode**. This has the following effect:
177 | - A `disabled` `LogsProvider` will disable logging for **all** children in the tree.
178 | - The selected `level` of the `LogsProvider` will serve as the minimum log level for nested children.
179 |
180 | Although deterministic, this is not useful for debugging. This is because it is sometimes useful to temporarily activate logging for select portion of a silenced log tree. To enable nested `LogProvider`s to ignore a parent's configuration, you can disable strict mode by passing a `strict={false}` prop:
181 |
182 | ```javascript
183 | <>
184 |
185 | {/* because the parent is not strict, we can enable the child tree */}
186 |
187 |
188 |
189 |
190 | {/* because the parent is not strict, we can log more granular information */}
191 |
192 |
193 |
194 | >
195 | ```
196 |
197 | ## ✌️ License
198 | [**MIT**](./LICENSE)
199 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # Xcode
6 | #
7 | build/
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | xcuserdata
17 | *.xccheckout
18 | *.moved-aside
19 | DerivedData
20 | *.hmap
21 | *.ipa
22 | *.xcuserstate
23 | project.xcworkspace
24 |
25 | # Android/IntelliJ
26 | #
27 | build/
28 | .idea
29 | .gradle
30 | local.properties
31 | *.iml
32 |
33 | # node.js
34 | #
35 | node_modules/
36 | npm-debug.log
37 | yarn-error.log
38 |
39 | # BUCK
40 | buck-out/
41 | \.buckd/
42 | *.keystore
43 |
44 | # fastlane
45 | #
46 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
47 | # screenshots whenever they are needed.
48 | # For more information about the recommended setup visit:
49 | # https://docs.fastlane.tools/best-practices/source-control/
50 |
51 | */fastlane/report.xml
52 | */fastlane/Preview.html
53 | */fastlane/screenshots
54 |
55 | # Bundle artifacts
56 | *.jsbundle
57 |
58 | # CocoaPods
59 | /ios/Pods/
60 |
61 | # Expo
62 | .expo/*
63 | web-build/
--------------------------------------------------------------------------------
/example/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "deepmerge",
4 | "loglevel"
5 | ]
6 | }
--------------------------------------------------------------------------------
/example/App.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { StyleSheet, Text, View, TouchableOpacity } from "react-native";
3 |
4 | import Logs, { useLogs, useLogLevelContext } from "react-use-logs";
5 |
6 | function Levels() {
7 | const logs = useLogs();
8 | const { levels } = useLogLevelContext();
9 | return (
10 |
11 | {levels.map((level: string, i: number) => (
12 | logs[level]("Hello", "World", e)}
15 | >
16 | {level}
17 |
18 | ))}
19 |
20 | );
21 | }
22 |
23 | export default function App() {
24 | return (
25 |
26 |
27 | Custom:
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | Defaults:
36 |
37 |
38 |
39 | Disabled:
40 |
41 |
42 |
43 |
44 |
45 | Info and up:
46 |
47 |
48 |
49 |
50 |
51 | Only Error:
52 |
53 |
54 |
55 |
56 |
57 | Custom Middleware:
58 | {
61 | alert(level);
62 | next();
63 | },
64 | ]}
65 | >
66 |
67 |
68 |
69 |
70 | Custom (Merged):
71 |
72 |
73 |
74 |
75 |
76 | Disabled state inheritance:
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | Level inheritance:
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | Disabled state inheritance (non-strict):
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | Level inheritance:
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | );
110 | }
111 |
112 | const styles = StyleSheet.create({
113 | container: {
114 | flex: 1,
115 | flexDirection: "row",
116 | backgroundColor: "#fff",
117 | alignItems: "flex-start",
118 | justifyContent: "center",
119 | },
120 | levels: {
121 | flexDirection: "column",
122 | padding: 10,
123 | },
124 | title: { fontWeight: "bold" },
125 | });
126 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # [TypeScript Example](https://www.typescriptlang.org/)
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | ```sh
13 | npx create-react-native-app -t with-typescript
14 | ```
15 |
16 |
17 | TypeScript is a superset of JavaScript which gives you static types and powerful tooling in Visual Studio Code including autocompletion and useful inline warnings for type errors.
18 |
19 | ## 🚀 How to use
20 |
21 | #### Creating a new project
22 |
23 | - Install the CLI: `npm i -g expo-cli`
24 | - Create a project: `expo init --template expo-template-blank-typescript`
25 | - `cd` into the project
26 |
27 | ### Adding TypeScript to existing projects
28 |
29 | - Copy the `tsconfig.json` from this repo, or new typescript template
30 | - Add typescript dependencies: `yarn add --dev @types/react @types/react-native @types/react-dom typescript`
31 | - Rename files tpo TypeScript, `.tsx` for React components and `.ts` for plain typescript files
32 |
33 | ## 📝 Notes
34 |
35 | - [Expo TypeScript guide](https://docs.expo.io/versions/latest/guides/typescript/)
36 |
--------------------------------------------------------------------------------
/example/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function(api) {
2 | api.cache(true);
3 | return {
4 | presets: ['babel-preset-expo'],
5 | };
6 | };
7 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "expo": "~39.0.0",
4 | "react": "~16.13.0",
5 | "react-dom": "~16.13.0",
6 | "react-native": "https://github.com/expo/react-native/archive/sdk-39.0.2.tar.gz",
7 | "react-native-screens": "~2.10.1",
8 | "react-native-web": "0.13.13",
9 | "react-use-logs": "../"
10 | },
11 | "devDependencies": {
12 | "@babel/core": "7.9.0",
13 | "@types/react": "~16.9.49",
14 | "@types/react-dom": "~16.9.8",
15 | "@types/react-native": "~0.63.20",
16 | "typescript": "~3.8.3"
17 | },
18 | "scripts": {
19 | "start": "expo start",
20 | "android": "expo start --android",
21 | "ios": "expo start --ios",
22 | "web": "expo web",
23 | "eject": "expo eject"
24 | },
25 | "private": true
26 | }
27 |
--------------------------------------------------------------------------------
/example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": true,
4 | "jsx": "react-native",
5 | "lib": ["dom", "esnext"],
6 | "moduleResolution": "node",
7 | "noEmit": true,
8 | "skipLibCheck": true,
9 | "resolveJsonModule": true
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-use-logs",
3 | "version": "0.2.0-alpha.0",
4 | "description": "⚛️ 🖨️ A hierarchy sensitive, middleware-defined console.log for React and React Native. ✨",
5 | "main": "dist",
6 | "repository": "https://github.com/cawfree/react-use-logs",
7 | "author": "Alex Thomas (@cawfree)
",
8 | "license": "MIT",
9 | "private": false,
10 | "scripts": {
11 | "build": "rm -rf dist ; yarn ; babel src --plugins @babel/plugin-transform-typescript --extensions '.ts,.tsx' --out-dir dist ; rm -rf node_modules",
12 | "prettify": "yarn prettier --write '**/*.tsx|**/*.ts'",
13 | "lint": "eslint --fix --ext .ts,.tsx ./src",
14 | "test": "jest"
15 | },
16 | "keywords": [
17 | "logs",
18 | "log",
19 | "console",
20 | "provider",
21 | "debug",
22 | "middleware",
23 | "react",
24 | "react-native"
25 | ],
26 | "devDependencies": {
27 | "@babel/cli": "^7.12.1",
28 | "@babel/core": "^7.12.3",
29 | "@babel/preset-env": "^7.12.1",
30 | "@babel/preset-react": "^7.12.1",
31 | "@babel/preset-typescript": "^7.12.1",
32 | "@types/react": "~16.9.49",
33 | "@types/react-dom": "~16.9.8",
34 | "@types/react-native": "~0.63.20",
35 | "@babel/plugin-transform-typescript": "^7.12.1",
36 | "husky": "^4.3.0",
37 | "jest": "^26.6.1",
38 | "leasot": "^11.3.0",
39 | "prettier": "^2.1.2",
40 | "prop-types": "^15.7.2",
41 | "react": "^17.0.1",
42 | "typescript": "~3.8.3"
43 | },
44 | "dependencies": {
45 | "deepmerge": "^4.2.2",
46 | "loglevel": "^1.7.0",
47 | "nanoid": "^3.1.15",
48 | "use-deep-compare-effect": "^1.4.0"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/constants/index.ts:
--------------------------------------------------------------------------------
1 | export { default as loglevel } from './loglevel';
--------------------------------------------------------------------------------
/src/constants/loglevel.ts:
--------------------------------------------------------------------------------
1 | import loglevel, { LogLevelDesc } from 'loglevel';
2 |
3 | const log = loglevel.getLogger('react-use-logs');
4 |
5 | log.setLevel(0 as LogLevelDesc);
6 |
7 | export default log;
8 |
--------------------------------------------------------------------------------
/src/contexts/LogLevelContext.ts:
--------------------------------------------------------------------------------
1 | import { createContext } from 'react';
2 |
3 | import { LogLevelContext } from '.';
4 |
5 | import { LogLevel } from '../middleware';
6 |
7 |
8 | export type LogsMiddleware = (level: string, messages: unknown[], next: () => void) => unknown;
9 |
10 | export const defaultLevels = Object.freeze([
11 | "trace",
12 | "debug",
13 | "info",
14 | "warn",
15 | "error",
16 | ]) as string[];
17 |
18 | export const [defaultLevel] = defaultLevels;
19 |
20 | export const defaultMiddleware = Object.freeze([LogLevel]) as LogsMiddleware[];
21 |
22 | export type LogLevelContextProps = {
23 | readonly levels: string[];
24 | readonly level: string;
25 | readonly middleware: LogsMiddleware[];
26 | readonly disabled: boolean;
27 | readonly parentId: string | null;
28 | readonly strict: boolean;
29 | };
30 |
31 | export const defaultContext = Object.freeze({
32 | levels: defaultLevels,
33 | level: defaultLevel,
34 | middleware: defaultMiddleware,
35 | disabled: false,
36 | parentId: null,
37 | strict: true,
38 | }) as LogLevelContextProps;
39 |
40 | export default createContext(defaultContext);
--------------------------------------------------------------------------------
/src/contexts/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | default as LogLevelContext,
3 | defaultContext,
4 | defaultMiddleware,
5 | defaultLevel,
6 | defaultLevels,
7 | } from "./LogLevelContext";
8 | export type { LogLevelContextProps, LogsMiddleware } from "./LogLevelContext";
9 |
--------------------------------------------------------------------------------
/src/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export { default as useLogLevelContext } from './useLogLevelContext';
2 | export { default as useLogs } from './useLogs';
--------------------------------------------------------------------------------
/src/hooks/useLogLevelContext.ts:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react';
2 |
3 | import { LogLevelContext } from '../contexts';
4 | import type { LogLevelContextProps } from '../contexts';
5 |
6 | export default function useLogLevelContext(): LogLevelContextProps {
7 | return useContext(LogLevelContext);
8 | }
--------------------------------------------------------------------------------
/src/hooks/useLogs.ts:
--------------------------------------------------------------------------------
1 | import { useState, useCallback } from 'react';
2 | import useDeepCompareEffect from 'use-deep-compare-effect';
3 |
4 | import { LogLevelContextProps, LogsMiddleware } from '../contexts';
5 | import { useLogLevelContext } from '.';
6 |
7 | const executeMiddlewareThunk = (middleware: LogsMiddleware[]) => async (level: string, messages: unknown[]) => {
8 | for await (let layer of middleware) {
9 | const result = await new Promise(async (resolve) => {
10 | try { await layer(level, messages, resolve); } catch (e) { resolve(e); }
11 | });
12 | if (result instanceof Error)
13 | break;
14 | }
15 | };
16 |
17 | export type useLogsResult = {
18 | [name: string]: (...messages : unknown[]) => void;
19 | };
20 |
21 | export default function useLogs(): useLogsResult {
22 | const { levels, level, middleware, disabled, parentId } = useLogLevelContext();
23 | const createLogs = useCallback(
24 | ({ levels, level, middleware, disabled }: LogLevelContextProps) => {
25 | const executeMiddleware = executeMiddlewareThunk(middleware);
26 | return levels.reduce(
27 | (obj, currentLevel) => {
28 | obj[currentLevel] = (...messages: unknown[]) => {
29 | if (!disabled && levels.indexOf(currentLevel) >= levels.indexOf(level)) {
30 | executeMiddleware(currentLevel, messages);
31 | }
32 | };
33 | return obj;
34 | },
35 | {},
36 | )
37 | },
38 | []
39 | );
40 | const [logs, setLogs] = useState(() =>
41 | createLogs({ levels, level, middleware, disabled, parentId })
42 | );
43 | useDeepCompareEffect(() => {
44 | setLogs(createLogs({ levels, level, middleware, disabled, parentId }));
45 | }, [levels, level, middleware, parentId, createLogs, setLogs]);
46 |
47 | return logs;
48 | }
49 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { LogLevelProvider as default } from "./providers";
2 | export { useLogs, useLogLevelContext } from "./hooks";
3 | export { LogLevel } from "./middleware";
4 |
--------------------------------------------------------------------------------
/src/middleware/LogLevel.ts:
--------------------------------------------------------------------------------
1 | import { loglevel } from '../constants';
2 | import type { LogsMiddleware } from '../contexts';
3 |
4 | export default function LogLevel(level: string, messages: string[], next: () => unknown): void {
5 | if (typeof loglevel[level] === 'function') {
6 | loglevel[level].call(this, ...messages);
7 | }
8 | next();
9 | }
10 |
--------------------------------------------------------------------------------
/src/middleware/index.ts:
--------------------------------------------------------------------------------
1 | export { default as LogLevel } from './LogLevel';
--------------------------------------------------------------------------------
/src/providers/LogLevelProvider.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import PropTypes from "prop-types";
3 | import deepmerge from "deepmerge";
4 | import { nanoid } from "nanoid/non-secure";
5 |
6 | import { loglevel } from '../constants';
7 | import { LogLevelContext, LogsMiddleware } from "../contexts";
8 | import type { LogLevelContextProps } from "../contexts";
9 | import { useLogLevelContext } from "../hooks";
10 |
11 | export type LogLevelProviderProps = {
12 | children: JSX.Element | JSX.Element[];
13 | levels: string[] | undefined;
14 | level: string | undefined;
15 | middleware: LogsMiddleware[] | undefined;
16 | strict: boolean;
17 | };
18 |
19 | // TODO: Move this to context.
20 | const supportedProps = Object.freeze([
21 | "levels",
22 | "level",
23 | "middleware",
24 | "disabled",
25 | ]) as string[];
26 |
27 | // TODO: Proper of level.
28 | function LogLevelProvider({
29 | children,
30 | strict: maybeStrict,
31 | ...extras
32 | }: LogLevelProviderProps): JSX.Element {
33 | const [id] = useState(nanoid);
34 | const { middleware: maybeMiddleware, levels: maybeLevels } = extras;
35 | const context = useLogLevelContext();
36 | const { disabled: parentIsDisabled, level: parentLevel, strict: parentIsStrict } = context;
37 | const value = deepmerge(
38 | context,
39 | Object.fromEntries(
40 | Object.entries(extras).filter(
41 | ([k, v]) => supportedProps.indexOf(k) >= 0 && v !== undefined
42 | )
43 | )
44 | ) as LogLevelContextProps;
45 | const {
46 | levels: maybeDuplicates,
47 | parentId,
48 | middleware,
49 | disabled: maybeDisabled,
50 | level: maybeLevel,
51 | ...extraValues
52 | } = value;
53 | const inheritedLevels = maybeDuplicates.filter(
54 | (e, i, orig) => orig.indexOf(e) === i
55 | ) as string[];
56 | const isTopLevel = parentId === null;
57 |
58 | const strict = maybeStrict !== undefined ? !!maybeStrict : parentIsStrict;
59 | const levels = isTopLevel && Array.isArray(maybeLevels) ? maybeLevels : inheritedLevels;
60 | const selectedLevel = levels.indexOf(maybeLevel) < 0 ? levels[0] : maybeLevel;
61 |
62 | if (selectedLevel !== maybeLevel) {
63 | loglevel.error(
64 | `react-use-logs: Encountered "${maybeLevel}", expected one of ${levels.join(',')}. Falling back to ${selectedLevel}.`
65 | );
66 | }
67 | const level = levels.indexOf(parentLevel) > levels.indexOf(maybeLevel) ? parentLevel : maybeLevel;
68 | return (
69 |
84 | {children}
85 |
86 | );
87 | }
88 |
89 | LogLevelProvider.propTypes = {
90 | levels: PropTypes.arrayOf(PropTypes.string),
91 | level: PropTypes.string,
92 | middleware: PropTypes.arrayOf(PropTypes.func),
93 | disabled: PropTypes.bool,
94 | strict: PropTypes.bool,
95 | };
96 |
97 | LogLevelProvider.defaultProps = {
98 | levels: undefined,
99 | level: undefined,
100 | middleware: undefined,
101 | disabled: undefined,
102 | strict: undefined,
103 | };
104 |
105 | export default LogLevelProvider;
106 |
--------------------------------------------------------------------------------
/src/providers/index.ts:
--------------------------------------------------------------------------------
1 | export { default as LogLevelProvider } from './LogLevelProvider';
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "noEmit": true,
4 | "jsx": "react"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------