@renpwn/simplelog 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 RENPWN
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.
package/README.md ADDED
@@ -0,0 +1,295 @@
1
+ # simpleLog
2
+
3
+ > Lightweight, opinionated, **TTY-aware logger** for Node.js with progress bar, file output, safe stringify, and zero dependencies.
4
+
5
+ [![NPM](https://img.shields.io/npm/v/@renpwn/simplelog)](https://www.npmjs.com/package/@renpwn/simplelog)
6
+ [![Downloads](https://img.shields.io/npm/dm/@renpwn/simplelog)](https://www.npmjs.com/package/@renpwn/simplelog)
7
+ [![License](https://img.shields.io/npm/l/@renpwn/simplelog)](LICENSE)
8
+
9
+ ---
10
+
11
+ ## โœจ Features
12
+
13
+ - ๐ŸŽจ Colored log levels (log, debug, info, warn, error)
14
+ - ๐Ÿง  Safe stringify (object โ†’ JSON, anti crash, truncate)
15
+ - ๐Ÿ•’ Timestamp with locale (`id`, `en`)
16
+ - ๐Ÿ“ File logging (TXT / JSONL + auto backup)
17
+ - ๐Ÿ“Š Multi progress bar (TTY-aware, auto redraw)
18
+ - ๐Ÿงน Non-TTY & CI safe
19
+ - โšก Zero dependencies
20
+ - ๐Ÿงฉ Modular & audit-friendly
21
+
22
+ ---
23
+
24
+ ## ๐Ÿ“ฆ Installation
25
+
26
+ ### NPM
27
+ ```bash
28
+ npm install @renpwn/simplelog
29
+ ```
30
+
31
+ ### Yarn
32
+ ```bash
33
+ yarn add @renpwn/simplelog
34
+ ```
35
+
36
+ ### Git Clone
37
+ ```bash
38
+ git clone https://github.com/renpwn/simpleLog.git
39
+ cd simpleLog
40
+ npm install
41
+ ```
42
+
43
+ ---
44
+
45
+ ## ๐Ÿš€ Quick Start (Minimal)
46
+
47
+ ```js
48
+ import { simpleLog } from '@renpwn/simplelog'
49
+
50
+ const log = simpleLog()
51
+
52
+ log.log('hello')
53
+ log.info('info message')
54
+ log.warn('warning')
55
+ log.error('error')
56
+ ```
57
+
58
+ ---
59
+
60
+ ## ๐Ÿง  Full Usage Example
61
+
62
+ ### 1๏ธโƒฃ Logger dengan Level, Warna & Waktu
63
+
64
+ ```js
65
+ import { simpleLog } from '@renpwn/simplelog'
66
+
67
+ const log = simpleLog({
68
+ level: 'debug', // log | debug | info | warn | error | silent
69
+ color: true, // enable ANSI color
70
+ time: {
71
+ locale: 'id', // id | en
72
+ position: 'prefix' // prefix | suffix
73
+ }
74
+ })
75
+
76
+ log.debug('Debug message')
77
+ log.info('Server started')
78
+ log.warn('Memory usage high')
79
+ log.error({ code: 500, msg: 'Fatal error' })
80
+ ```
81
+
82
+ ๐Ÿ“Œ **Keterangan**
83
+ - `level` โ†’ filter minimum level yang ditampilkan
84
+ - `color` โ†’ otomatis nonaktif jika non-TTY
85
+ - `time` โ†’ format waktu ringkas & konsisten
86
+
87
+ ---
88
+
89
+ ### 2๏ธโƒฃ Safe Stringify & Truncate
90
+
91
+ ```js
92
+ const log = simpleLog({
93
+ truncate: {
94
+ maxLength: 200
95
+ }
96
+ })
97
+
98
+ log.info({
99
+ veryLongData: 'x'.repeat(1000)
100
+ })
101
+ ```
102
+
103
+ ๐Ÿ“Œ Object akan di-`JSON.stringify`, dan otomatis dipotong jika terlalu panjang.
104
+
105
+ ---
106
+
107
+ ### 3๏ธโƒฃ File Logging (TXT & JSONL)
108
+
109
+ #### TXT (default)
110
+ ```js
111
+ const log = simpleLog({
112
+ file: {
113
+ path: 'logs/app.log'
114
+ }
115
+ })
116
+
117
+ log.info('App started')
118
+ ```
119
+
120
+ Output:
121
+ ```
122
+ [2026-01-20T07:21:10.120Z] INFO App started
123
+ ```
124
+
125
+ #### JSONL
126
+ ```js
127
+ const log = simpleLog({
128
+ file: {
129
+ path: 'logs/app.json',
130
+ format: 'json'
131
+ }
132
+ })
133
+ ```
134
+
135
+ Output:
136
+ ```json
137
+ {"time":"2026-01-20T07:21:10.120Z","level":"info","message":"App started"}
138
+ ```
139
+
140
+ ๐Ÿ“Œ File write aman dengan auto-backup `.bak`.
141
+
142
+ ---
143
+
144
+ ### 4๏ธโƒฃ Progress Bar (Multi Slot)
145
+
146
+ ```js
147
+ const log = simpleLog({
148
+ progress: {
149
+ slots: [
150
+ ['Scraping', { color: 'cyan' }],
151
+ ['DB Queue', 'auto']
152
+ ]
153
+ }
154
+ })
155
+
156
+ let i = 0
157
+ const timer = setInterval(() => {
158
+ i++
159
+ log.updateProgress('Scraping', i, 10, 'fetching...')
160
+ log.updateProgress('DB Queue', i * 2, 20)
161
+
162
+ if (i >= 10) {
163
+ clearInterval(timer)
164
+ log.info('All jobs finished')
165
+ }
166
+ }, 300)
167
+ ```
168
+
169
+ ๐Ÿ“Œ **Catatan**
170
+ - Progress hanya muncul di TTY
171
+ - Log biasa akan membersihkan progress lalu merender ulang
172
+
173
+ ---
174
+
175
+ ### 5๏ธโƒฃ Custom Progress Theme
176
+
177
+ ```js
178
+ const log = simpleLog({
179
+ progress: {
180
+ slots: ['Download'],
181
+ theme: {
182
+ size: 30,
183
+ filled: 'โ–ˆ',
184
+ empty: 'โ–‘',
185
+ left: '[',
186
+ right: ']',
187
+ style: { color: 'green' }
188
+ }
189
+ }
190
+ })
191
+ ```
192
+
193
+ ---
194
+
195
+ ## ๐Ÿงฉ API Ringkas
196
+
197
+ ```js
198
+ log.log(...args)
199
+ log.debug(...args)
200
+ log.info(...args)
201
+ log.warn(...args)
202
+ log.error(...args)
203
+
204
+ log.updateProgress(name, cur, total, text?)
205
+ log.removeProgress(name)
206
+ ```
207
+
208
+ ---
209
+
210
+ ## ๐Ÿง  Architecture
211
+
212
+ ### console.log
213
+ ```
214
+ console.log()
215
+ โ†“
216
+ stdout
217
+ ```
218
+
219
+ โŒ No level
220
+ โŒ No file
221
+ โŒ No progress
222
+ โŒ No safety
223
+
224
+ ---
225
+
226
+ ### simpleLog
227
+ ```
228
+ simpleLog()
229
+ โ”‚
230
+ โ”œโ”€ Levels (filter)
231
+ โ”œโ”€ Time formatter
232
+ โ”œโ”€ Safe stringify
233
+ โ”œโ”€ ANSI formatter
234
+ โ”œโ”€ FileSink (txt / jsonl)
235
+ โ”‚
236
+ โ””โ”€ ProgressManager
237
+ โ””โ”€ ProgressRenderer
238
+ โ†“
239
+ stdout (TTY aware)
240
+ ```
241
+
242
+ ---
243
+
244
+ ## ๐Ÿ“‚ Project Structure
245
+
246
+ ```
247
+ simplelog/
248
+ โ”œโ”€ package.json
249
+ โ””โ”€ src/
250
+ โ”œโ”€ index.js # entry point (simpleLog)
251
+ โ”‚
252
+ โ”œโ”€ Logger.js # logger utama
253
+ โ”œโ”€ Levels.js # level & style
254
+ โ”œโ”€ Formatter.js # ANSI formatter
255
+ โ”œโ”€ Stringify.js # stringify + truncate
256
+ โ”œโ”€ Time.js # time formatter
257
+ โ”œโ”€ FileSink.js # file logging
258
+ โ”‚
259
+ โ””โ”€ Progress/
260
+ โ”œโ”€ ProgressManager.js # progress state
261
+ โ””โ”€ ProgressRenderer.js # progress bar renderer
262
+ ```
263
+
264
+ ---
265
+
266
+ ## ๐Ÿง  Design Philosophy
267
+
268
+ - Small core
269
+ - No dependency
270
+ - Predictable output
271
+ - Audit friendly
272
+ - Library-first design
273
+
274
+ Cocok untuk:
275
+ - CLI tools
276
+ - Bot WhatsApp / Telegram
277
+ - Scraper
278
+ - Worker / queue
279
+ - Base library (`simpleStore`, `simpleFetch`, dll)
280
+
281
+ ---
282
+
283
+ ## ๐Ÿ”— Links
284
+
285
+ - GitHub
286
+ https://github.com/renpwn/simpleLog
287
+
288
+ - NPM
289
+ https://www.npmjs.com/package/@renpwn/simplelog
290
+
291
+ ---
292
+
293
+ ## ๐Ÿ“„ License
294
+
295
+ MIT ยฉ RenPwn
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@renpwn/simplelog",
3
+ "version": "0.0.1",
4
+ "description": "Lightweight, safe, audit-friendly logger for CLI tools and long-running Node.js processes",
5
+ "type": "module",
6
+ "main": "./src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js"
9
+ },
10
+ "files": [
11
+ "src"
12
+ ],
13
+ "keywords": [
14
+ "logger",
15
+ "logging",
16
+ "cli",
17
+ "terminal",
18
+ "progress-bar",
19
+ "jsonl",
20
+ "ndjson",
21
+ "tty",
22
+ "node"
23
+ ],
24
+ "license": "MIT",
25
+ "author": "RenPwn",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/renpwn/simpleLog.git"
29
+ },
30
+ "homepage": "https://github.com/renpwn/simpleLog",
31
+ "bugs": {
32
+ "url": "https://github.com/renpwn/simpleLog/issues"
33
+ },
34
+ "sideEffects": false,
35
+ "engines": {
36
+ "node": ">=16"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ }
41
+ }
@@ -0,0 +1,51 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+
4
+ export class FileSink {
5
+ constructor(opts = {}) {
6
+ if (!opts.path) {
7
+ this.enabled = false
8
+ return
9
+ }
10
+
11
+ this.enabled = true
12
+ this.format = opts.format || 'txt' // txt | json (JSONL)
13
+ this.backup = opts.backup !== false
14
+ this.basePath = path.resolve(process.cwd(), opts.path)
15
+
16
+ fs.mkdirSync(path.dirname(this.basePath), { recursive: true })
17
+ }
18
+
19
+ backupFile(file) {
20
+ if (!this.backup || !fs.existsSync(file)) return
21
+ fs.copyFileSync(file, file + '.bak')
22
+ }
23
+
24
+ restoreFile(file) {
25
+ const bak = file + '.bak'
26
+ if (fs.existsSync(bak)) fs.copyFileSync(bak, file)
27
+ }
28
+
29
+ write(level, message) {
30
+ if (!this.enabled) return
31
+ const time = new Date().toISOString()
32
+
33
+ try {
34
+ if (this.format === 'json') {
35
+ this.backupFile(this.basePath)
36
+ fs.appendFileSync(
37
+ this.basePath,
38
+ JSON.stringify({ time, level, message }) + '\n'
39
+ )
40
+ return
41
+ }
42
+
43
+ fs.appendFileSync(
44
+ this.basePath,
45
+ `[${time}] ${level.toUpperCase()} ${message}\n`
46
+ )
47
+ } catch {
48
+ this.restoreFile(this.basePath)
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,22 @@
1
+ const COLORS = {
2
+ black:30, red:31, green:32, yellow:33,
3
+ blue:34, magenta:35, cyan:36, white:37
4
+ }
5
+ const BG = {
6
+ black:40, red:41, green:42, yellow:43,
7
+ blue:44, magenta:45, cyan:46, white:47
8
+ }
9
+
10
+ export function format(text, style, tty = true) {
11
+ if (!tty || !style) return text
12
+
13
+ const codes = []
14
+ if (style.bold) codes.push(1)
15
+ if (style.dim) codes.push(2)
16
+ if (style.color && COLORS[style.color]) codes.push(COLORS[style.color])
17
+ if (style.bg && BG[style.bg]) codes.push(BG[style.bg])
18
+
19
+ return codes.length
20
+ ? `\x1b[${codes.join(';')}m${text}\x1b[0m`
21
+ : text
22
+ }
package/src/Levels.js ADDED
@@ -0,0 +1,20 @@
1
+ export const LEVELS = {
2
+ log: 0,
3
+ debug: 10,
4
+ info: 20,
5
+ warn: 30,
6
+ error: 40,
7
+ silent: 99
8
+ }
9
+
10
+ export const LEVEL_STYLE = {
11
+ log: { },
12
+ debug: { color: 'cyan', dim: true },
13
+ info: { color: 'green' },
14
+ warn: { color: 'yellow', bold: true },
15
+ error: { color: 'white', bg: 'red', bold: true }
16
+ }
17
+
18
+ export function normalizeLevel(level = 'log') {
19
+ return LEVELS[level] !== undefined ? level : 'log'
20
+ }
package/src/Logger.js ADDED
@@ -0,0 +1,117 @@
1
+ import { LEVELS, LEVEL_STYLE, normalizeLevel } from './Levels.js'
2
+ import { createStringifier } from './Stringify.js'
3
+ import { formatTime } from './Time.js'
4
+ import { format } from './Formatter.js'
5
+ import { FileSink } from './FileSink.js'
6
+ import { ProgressManager } from './Progress/ProgressManager.js'
7
+
8
+ export class Logger {
9
+ constructor(opts = {}) {
10
+ this.level = normalizeLevel(opts.level)
11
+ this.color = !!opts.color
12
+ this.tty = process.stdout.isTTY && !process.env.CI
13
+
14
+ this.time = !!opts.time
15
+ this.timeLocale = opts.time?.locale || 'id'
16
+ this.timePos = opts.time?.position || 'prefix'
17
+
18
+ this.stringify = createStringifier(
19
+ opts.truncate || { maxLength: opts.maxLength }
20
+ )
21
+
22
+ this.file = new FileSink(opts.file || {})
23
+
24
+ this.progress = opts.progress
25
+ ? new ProgressManager(opts.progress.slots || [], opts.progress.theme)
26
+ : null
27
+
28
+ this.lastProgressLines = 0
29
+ }
30
+
31
+ allow(type) {
32
+ return LEVELS[type] >= LEVELS[this.level]
33
+ }
34
+
35
+ style(type, user) {
36
+ if (!this.color) return user
37
+ return { ...LEVEL_STYLE[type], ...user }
38
+ }
39
+
40
+ /* ================= PROGRESS API ================= */
41
+
42
+ updateProgress(name, cur, total, text) {
43
+ if (!this.progress) return
44
+ this.progress.update(name, cur, total, text)
45
+ this.renderProgress()
46
+ }
47
+
48
+ removeProgress(name) {
49
+ if (!this.progress) return
50
+ this.progress.remove(name)
51
+ this.renderProgress()
52
+ }
53
+
54
+ /* ================= RENDER CONTROL ================= */
55
+
56
+ clearProgress() {
57
+ if (!this.progress || !this.tty || this.lastProgressLines === 0) return
58
+
59
+ process.stdout.write(`\x1b[${this.lastProgressLines}A`)
60
+ for (let i = 0; i < this.lastProgressLines; i++) {
61
+ process.stdout.write('\x1b[2K')
62
+ process.stdout.write('\x1b[1B')
63
+ }
64
+ process.stdout.write(`\x1b[${this.lastProgressLines}A`)
65
+ this.lastProgressLines = 0
66
+ }
67
+
68
+ renderProgress() {
69
+ if (!this.progress || !this.tty) return
70
+
71
+ const snapshot = this.progress.snapshot()
72
+ if (!snapshot.length) return
73
+
74
+ this.clearProgress()
75
+
76
+ process.stdout.write('\n')
77
+ for (const p of snapshot) {
78
+ process.stdout.write(`${p.line}\n`)
79
+ }
80
+
81
+ this.lastProgressLines = snapshot.length + 1
82
+ }
83
+
84
+ /* ================= CORE WRITE ================= */
85
+
86
+ write(type, args, style) {
87
+ if (!this.allow(type)) return
88
+
89
+ const msg = args.map(this.stringify.toStr).join(' ')
90
+ const t = this.time ? formatTime(this.timeLocale) : null
91
+
92
+ const out =
93
+ t && this.timePos === 'suffix'
94
+ ? `${msg} | ${t}`
95
+ : t
96
+ ? `${t} ${msg}`
97
+ : msg
98
+
99
+ if (this.progress) {
100
+ this.clearProgress()
101
+ process.stdout.write(
102
+ format(out, this.style(type, style), this.tty) + '\n'
103
+ )
104
+ this.renderProgress()
105
+ } else {
106
+ console.log(format(out, this.style(type, style), this.tty))
107
+ }
108
+
109
+ this.file.write(type, out)
110
+ }
111
+
112
+ log(...a) { this.write('log', a) }
113
+ debug(...a) { this.write('debug', a) }
114
+ info(...a) { this.write('info', a) }
115
+ warn(...a) { this.write('warn', a) }
116
+ error(...a) { this.write('error', a) }
117
+ }
@@ -0,0 +1,62 @@
1
+ import { ProgressRenderer } from './ProgressRenderer.js'
2
+
3
+ export class ProgressManager {
4
+ constructor(slots = [], theme = {}) {
5
+ this.slots = slots
6
+ this.state = new Map()
7
+ this.renderer = new ProgressRenderer(theme)
8
+ this.theme = theme?.style || null
9
+ }
10
+
11
+ update(name, cur, total, text = "") {
12
+ this.state.set(name, {
13
+ cur,
14
+ total: total < cur ? cur : total,
15
+ text,
16
+ start: Date.now()
17
+ })
18
+ }
19
+
20
+ remove(name) {
21
+ this.state.delete(name)
22
+ }
23
+
24
+ clear() {
25
+ this.state.clear()
26
+ }
27
+
28
+ snapshot() {
29
+ const s = a => Array.isArray(a) ? a : []
30
+
31
+ return this.slots.map(item => {
32
+ const name = s(item)[0] || item
33
+ const vStyle = s(item)[1] || { }
34
+
35
+ const v = this.state.get(name) || { cur: 0, total: 0, text: '' }
36
+
37
+ const percent = v.total
38
+ ? Math.floor((v.cur / v.total) * 100)
39
+ : 0
40
+
41
+ const elapsed = v.start
42
+ ? Math.floor((Date.now() - v.start) / 1000)
43
+ : 0
44
+
45
+ const eta =
46
+ v.cur && v.total && elapsed
47
+ ? Math.floor((elapsed / v.cur) * (v.total - v.cur))
48
+ : null
49
+
50
+ return {
51
+ name,
52
+ cur: v.cur,
53
+ total: v.total,
54
+ percent,
55
+ elapsed,
56
+ eta,
57
+ //text: v.text,
58
+ line: this.renderer.render(name, v.cur, v.total, v.text, vStyle)
59
+ }
60
+ })
61
+ }
62
+ }
@@ -0,0 +1,48 @@
1
+ import { format } from '../Formatter.js'
2
+
3
+ export class ProgressRenderer {
4
+ constructor({ size = 20, filled = 'โ–ˆ', empty = 'โ–‘', style = {}, left = '[', right = ']'} = {}) {
5
+ this.size = size
6
+ this.filled = filled
7
+ this.empty = empty
8
+ this.style = style
9
+ this.left = left
10
+ this.right = right
11
+ this.tty = process.stdout.isTTY && !process.env.CI
12
+ }
13
+
14
+ styleResolve(style, current, percent){
15
+ if(!style) return null
16
+
17
+ //auto style
18
+ if (style === 'auto')
19
+ return percent >= 85
20
+ ? { color: 'red', bold: true }
21
+ : percent >= 45
22
+ ? { color: 'yellow' }
23
+ : { color: 'blue' }
24
+
25
+ //format from style dual or single
26
+ style = Array.isArray(style) ? (current > 0 && style.length > 1 ? style[1] : style[0]) : style
27
+ return style && typeof style === 'object' && Object.keys(style).length ? style : null
28
+ }
29
+
30
+ render(name, cur, total, text, style = {}) {
31
+ if (!total) total = 1
32
+ const percent = Math.floor((cur / total) * 100)
33
+ const filled = Math.min(
34
+ this.size,
35
+ Math.max(0, Math.floor(percent / 100 * this.size))
36
+ )
37
+
38
+ style = this.styleResolve(style, cur, percent)
39
+ this.style = this.styleResolve(this.style, cur)
40
+
41
+ const left = format(`${name} ${this.left}`, this.style, this.tty)
42
+ const right = format(`${this.right} ${percent}% ${text}`, this.style, this.tty)
43
+ const bar = format(this.filled.repeat(filled) + this.empty.repeat(this.size - filled), style, this.tty)
44
+
45
+
46
+ return left + bar + right
47
+ }
48
+ }
@@ -0,0 +1,26 @@
1
+ export function createStringifier(opts = {}) {
2
+ const enabled = opts.enabled !== false
3
+ const max = Number.isInteger(opts.maxLength) ? opts.maxLength : 500
4
+
5
+ function truncate(str) {
6
+ if (!enabled) return str
7
+ if (str.length > max) {
8
+ return str.slice(0, max) +
9
+ `... [TRUNCATED ${str.length - max} chars]`
10
+ }
11
+ return str
12
+ }
13
+
14
+ function toStr(v) {
15
+ if (typeof v === 'object' && v !== null) {
16
+ try {
17
+ return truncate(JSON.stringify(v, null, 2))
18
+ } catch {
19
+ return truncate(String(v))
20
+ }
21
+ }
22
+ return truncate(String(v))
23
+ }
24
+
25
+ return { toStr }
26
+ }
package/src/Time.js ADDED
@@ -0,0 +1,17 @@
1
+ const LOCALES = {
2
+ id: {
3
+ days: ["MIN","SEN","SEL","RAB","KAM","JUM","SAB"],
4
+ mons: ["JAN","FEB","MAR","APR","MEI","JUN","JUL","AGS","SEP","OKT","NOV","DES"]
5
+ },
6
+ en: {
7
+ days: ["SUN","MON","TUE","WED","THU","FRI","SAT"],
8
+ mons: ["JAN","FEB","MAR","APR","MAY","JUN","JUL","AUG","SEP","OCT","NOV","DEC"]
9
+ }
10
+ }
11
+
12
+ export function formatTime(locale = 'id', date = new Date()) {
13
+ const l = LOCALES[locale] || LOCALES.id
14
+ const d = date
15
+
16
+ return `${l.days[d.getDay()]}|${String(d.getDate()).padStart(2,'0')}.${l.mons[d.getMonth()]}|${d.toTimeString().split(' ')[0]}`
17
+ }
package/src/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import { Logger } from './Logger.js'
2
+
3
+ export function simpleLog(options) {
4
+ return new Logger(options)
5
+ }