├── .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 | Supports Expo iOS 6 | 7 | Supports Expo Android 8 | 9 | Supports Expo Web 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 | --------------------------------------------------------------------------------