├── page ├── .firebaserc └── public │ ├── assets │ ├── js │ │ ├── script.js │ │ ├── watch.js │ │ └── components.js │ └── css │ │ ├── watch.css │ │ ├── progress.css │ │ └── style.css │ ├── watch │ └── index.html │ └── index.html ├── .gitignore ├── package.json ├── lib ├── remote-logger.min.js └── remote-logger.js └── README.md /page/.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "schirrel" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | firebase.js 2 | firebase.json 3 | firestore.rules 4 | firestore.indexes.json 5 | 6 | **/firebase.js 7 | **/firebase.json 8 | **/firestore.rules 9 | **/firestore.indexes.json 10 | 11 | **/.firebase 12 | node_modules -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@schirrel/remote-logger", 3 | "version": "1.2.0", 4 | "description": "View remote console and error from anywhere", 5 | "main": "/lib/remote-logger.min.js", 6 | "directories": { 7 | "lib": "lib" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Fake Test\"", 11 | "minify": " minify lib/remote-logger.js > lib/remote-logger.min.js" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/schirrel/remote-logger.git" 16 | }, 17 | "author": "", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/schirrel/remote-logger/issues" 21 | }, 22 | "homepage": "https://github.com/schirrel/remote-logger#readme", 23 | "dependencies": { 24 | "minify": "^10.3.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /page/public/assets/js/script.js: -------------------------------------------------------------------------------- 1 | const toast = document.querySelector("#toast"); 2 | const loading = document.querySelector("#loading"); 3 | const generateButton = document.querySelector("#generateButton"); 4 | const loggerId = document.querySelector("#loggerId"); 5 | 6 | const db = firebase.firestore(); 7 | 8 | generateButton.addEventListener("click", () => { 9 | loading.open = true; 10 | db.collection("logger") 11 | .add({}) 12 | .then((docRef) => { 13 | loggerId.value = docRef.id; 14 | }) 15 | .finally(() => { 16 | loading.open = false; 17 | }); 18 | }); 19 | const clipboard = new ClipboardJS("#copyButton"); 20 | 21 | clipboard.on("success", function (e) { 22 | toast.open = true; 23 | setTimeout(() => { 24 | toast.open = false; 25 | }, 2000); 26 | e.clearSelection(); 27 | }); 28 | 29 | clipboard.on("error", function (e) { 30 | console.error("Action:", e.action); 31 | console.error("Trigger:", e.trigger); 32 | }); 33 | -------------------------------------------------------------------------------- /lib/remote-logger.min.js: -------------------------------------------------------------------------------- 1 | var{keys:a}=Object;function b(A,_={}){var c=_.only?.length,e=B=>d({loggerId:A,args:B,info:{oscpu:window.navigator.oscpu,userAgent:window.navigator.userAgent,appCodeName:window.navigator.appCodeName,appName:window.navigator.appName,appVersion:window.navigator.appVersion,product:window.navigator.product,platform:window.navigator.platform,vendor:window.navigator.vendor}}),f=a(console);function d(B={}){var _c=new XMLHttpRequest();_c.withCredentials=!1;_c.open("POST","https://us-central1-schirrel.cloudfunctions.net/logger");_c.setRequestHeader("Content-Type","application/json");_c.send(JSON.stringify(B))}c&&(f=f.filter(key=>_.only.some(only=>only==key)));for(const B of f){var g=console[B];console[B]=()=>{e({arguments,type:B,date:new Date()});g.apply(console,arguments)}}if(c&&!_.only.some(option=>option=="error"))return;addEventListener("error",evt=>e({arguments:{error:evt.error,filename:evt.filename,line:evt.lineno,message:evt.message},type:"exception",date:new Date()}))}window.DebugRemoteLogger=b; 2 | -------------------------------------------------------------------------------- /page/public/assets/css/watch.css: -------------------------------------------------------------------------------- 1 | main { 2 | justify-content: start; 3 | padding-top: 20px; 4 | } 5 | 6 | #watchButton { 7 | background-color: #1f4369; 8 | border: 2px solid #ffaf23; 9 | color: white; 10 | border-radius: 0 16px; 11 | margin-left: 8px; 12 | } 13 | 14 | section { 15 | background-color: white; 16 | } 17 | 18 | ul { 19 | list-style: none; 20 | padding: 4px; 21 | gap: 2px; 22 | display: flex; 23 | flex-direction: column; 24 | } 25 | 26 | .warn { 27 | color: black; 28 | background: gold; 29 | } 30 | .error { 31 | color: white; 32 | background: crimson; 33 | } 34 | .info { 35 | color: black; 36 | background: cornflowerblue; 37 | } 38 | .debug { 39 | color: black; 40 | background: lightseagreen; 41 | } 42 | 43 | .item { 44 | padding: 4px; 45 | max-width: fit-content; 46 | white-space: pre-line; 47 | word-wrap: break-word; 48 | } 49 | pre { 50 | max-width: fit-content; 51 | white-space: pre-line; 52 | word-wrap: break-word; 53 | } 54 | table { 55 | min-width: 100vw; 56 | max-width: 100vw; 57 | border-collapse: collapse; 58 | } 59 | 60 | 61 | td { 62 | padding: 4px; 63 | vertical-align: top; 64 | } 65 | th, td, tr { 66 | border: 1px solid darkslategray; 67 | } 68 | 69 | th { 70 | background-color: wheat; 71 | } -------------------------------------------------------------------------------- /page/public/watch/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | DebugRemoteLogger 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /page/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | DebugRemoteLogger 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 |
18 | 21 | 22 |
23 | Watch your logs 24 | 25 | 31 | 32 | ID copied 33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /page/public/assets/css/progress.css: -------------------------------------------------------------------------------- 1 | dialog#loading { 2 | width: 100%; 3 | height: 100%; 4 | top: 0; 5 | z-index: 99; 6 | background: #488ad2f0; 7 | justify-content: center; 8 | align-items: center; 9 | } 10 | 11 | dialog#loading[open] { 12 | display: flex; 13 | } 14 | 15 | @keyframes progress-loading { 16 | 50% { 17 | background-position: left; 18 | } 19 | } 20 | progress { 21 | --_radius: 1e3px; 22 | 23 | --_track: #ddd; 24 | --_progress: #ffaf23; 25 | --_indeterminate-track: linear-gradient( 26 | to right, 27 | var(--_track) 45%, 28 | var(--_progress) 0%, 29 | var(--_progress) 55%, 30 | var(--_track) 0% 31 | ); 32 | --_indeterminate-track-size: 225% 100%; 33 | --_indeterminate-track-animation: progress-loading 2s infinite ease; 34 | 35 | /* reset */ 36 | appearance: none; 37 | border: none; 38 | 39 | position: relative; 40 | height: 18px; 41 | border-radius: 8px 0 8px 0; 42 | overflow: hidden; 43 | } 44 | 45 | /* Safari/Chromium */ 46 | progress[value]::-webkit-progress-bar { 47 | background-color: var(--_track); 48 | } 49 | 50 | progress[value]::-webkit-progress-value { 51 | background-color: var(--_progress); 52 | } 53 | 54 | progress:indeterminate::after { 55 | content: ""; 56 | inset: 0; 57 | position: absolute; 58 | background: var(--_indeterminate-track); 59 | background-size: var(--_indeterminate-track-size); 60 | background-position: right; 61 | animation: var(--_indeterminate-track-animation); 62 | } 63 | 64 | progress:indeterminate::-webkit-progress-bar { 65 | background: var(--_indeterminate-track); 66 | background-size: var(--_indeterminate-track-size); 67 | background-position: right; 68 | animation: var(--_indeterminate-track-animation); 69 | } 70 | 71 | progress:indeterminate::-moz-progress-bar { 72 | background: var(--_indeterminate-track); 73 | background-size: var(--_indeterminate-track-size); 74 | background-position: right; 75 | animation: var(--_indeterminate-track-animation); 76 | } 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Remote Logger 2 | See `console.log` and its _siblings_ as well as exceptions throw from another place. 3 | 4 | Initially develop to help on mobile debug of logs, mostly for Safari or iOS Browser. But you can use as you wish. 5 | 6 | Logs may live no more than 3/4 days at our database. 7 | 8 | ![RemoteLogger](https://user-images.githubusercontent.com/6757777/216325507-650b2213-55d9-4a2d-956e-94670ef7f522.gif) 9 | 10 | 11 | # Install 12 | 13 | ```sh 14 | npm i @schirrel/remote-logger 15 | ``` 16 | 17 | ```sh 18 | yarn add @schirrel/remote-logger 19 | ``` 20 | ## CDN 21 | ```sh 22 | https://cdn.jsdelivr.net/gh/schirrel/remote-logger@main/lib/remote-logger.min.js 23 | ``` 24 | 25 | ## How it works 26 | Generate your logger id at https://remote-logger.web.app/ 27 | 28 | Setup it on you app and watch the loggers at [Watcher](https://remote-logger.web.app/watch). 29 | 30 | ## Instalation 31 | 32 | ## Usage 33 | 34 | If installed with npm you need to import 35 | ```js 36 | import '@schirrel/remote-logger/lib/remote-logger'; 37 | ``` 38 | 39 | 40 | The `DebugRemoteLogger` function is available globally. 41 | 42 | ```js 43 | DebugRemoteLogger(id: string , options?: {only: []}) 44 | ``` 45 | - `id` string is required 46 | - `options` object is options, and currenty has only a single option: 47 | - `only` array of string that should match console levels from `Object.keys(console)`: 48 | - 'debug', 'error', 'info', 'log', 'warn', 'dir', 'dirxml', 'table', 'trace', 'group', 'groupCollapsed', 'groupEnd', 'clear', 'count', 'countReset', 'assert', 'profile', 'profileEnd', 'time', 'timeLog', 'timeEnd', 'timeStamp', 'context', 'createTask', 'memory'] 49 | 50 | 51 | ```js 52 | DebugRemoteLogger("your-generated-id") 53 | // with options 54 | DebugRemoteLogger("your-generated-id", { only: ['info']) 55 | DebugRemoteLogger("your-generated-id", { only: ['warn', 'error']) 56 | DebugRemoteLogger("your-generated-id", { only: ['debug']) 57 | DebugRemoteLogger("your-generated-id", { only: ['error']) 58 | ``` 59 | 60 | -------------------------------------------------------------------------------- /lib/remote-logger.js: -------------------------------------------------------------------------------- 1 | function DebugRemoteLogger(loggerId, options = {}) { 2 | var optionsDefined = options.only && options.only.length; 3 | 4 | function postData(data = {}) { 5 | var body = JSON.stringify(data); 6 | 7 | var xhr = new XMLHttpRequest(); 8 | xhr.withCredentials = false; 9 | 10 | xhr.open("POST", "https://us-central1-schirrel.cloudfunctions.net/logger"); 11 | xhr.setRequestHeader("Content-Type", "application/json"); 12 | 13 | xhr.send(body); 14 | } 15 | 16 | var communicate = (args) => { 17 | var data = { 18 | loggerId: loggerId, 19 | args: args, 20 | info: { 21 | oscpu: window.navigator.oscpu, 22 | userAgent: window.navigator.userAgent, 23 | appCodeName: window.navigator.appCodeName, 24 | appName: window.navigator.appName, 25 | appVersion: window.navigator.appVersion, 26 | product: window.navigator.product, 27 | platform: window.navigator.platform, 28 | vendor: window.navigator.vendor, 29 | }, 30 | }; 31 | postData(data); 32 | }; 33 | 34 | var consoleKeys = Object.keys(console); 35 | 36 | if (optionsDefined) { 37 | consoleKeys = consoleKeys.filter((key) => 38 | options.only.some((only) => only == key) 39 | ); 40 | } 41 | 42 | consoleKeys.forEach((logType) => { 43 | var sourceLog = console[logType]; 44 | 45 | console[logType] = function () { 46 | communicate({ arguments, type: logType, date: new Date() }); 47 | sourceLog.apply(console, arguments); 48 | }; 49 | }); 50 | 51 | if (optionsDefined && !options.only.some((option) => option == "error")) 52 | return; 53 | 54 | addEventListener("error", (evt) => { 55 | communicate({ 56 | arguments: { 57 | error: evt.error, 58 | filename: evt.filename, 59 | line: evt.lineno, 60 | message: evt.message, 61 | }, 62 | type: "exception", 63 | date: new Date(), 64 | }); 65 | }); 66 | } 67 | 68 | window.DebugRemoteLogger = DebugRemoteLogger; 69 | -------------------------------------------------------------------------------- /page/public/assets/css/style.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | font-family: sans-serif; 4 | padding: 0; 5 | margin: 0; 6 | } 7 | 8 | body { 9 | min-height: 100vh; 10 | display: grid; 11 | grid-auto-rows: 50px calc(100% - 100px) 50px; 12 | } 13 | 14 | main { 15 | align-items: center; 16 | display: flex; 17 | background: #488ad2; 18 | flex-direction: column; 19 | justify-content: center; 20 | gap: 20px; 21 | min-height: calc(100% - 100px); 22 | position: relative; 23 | } 24 | button { 25 | pointer-events: all; 26 | cursor: pointer; 27 | font-weight: bold; 28 | 29 | transition: color 300ms ease-in-out, background 300ms ease-in-out, 30 | border 300ms ease-in-out; 31 | will-change: color, border, background; 32 | } 33 | 34 | #generateButton { 35 | padding: 8px; 36 | background: transparent; 37 | border: 2px solid #1f4369; 38 | border-radius: 16px 0 16px 0; 39 | color: #333; 40 | max-width: 200px; 41 | font-size: 18px; 42 | } 43 | #generateButton:hover { 44 | background: #1f4369; 45 | color: white; 46 | } 47 | 48 | label { 49 | display: block; 50 | } 51 | 52 | #copyButton { 53 | padding: 12px; 54 | border: none; 55 | background: #dddddd; 56 | } 57 | input { 58 | padding: 12px; 59 | background: #dddddd; 60 | width: 300px; 61 | border: 2px solid transparent; 62 | border-radius: 16px 0 16px 0; 63 | 64 | transition: color 300ms ease-in-out, background 300ms ease-in-out, 65 | border 300ms ease-in-out; 66 | will-change: color, border, background; 67 | } 68 | 69 | .input-content { 70 | display: flex; 71 | gap: 0; 72 | position: relative; 73 | } 74 | 75 | #copyButton { 76 | position: absolute; 77 | right: 6px; 78 | top: 2px; 79 | background: transparent; 80 | } 81 | 82 | #copyButton:hover { 83 | color: #488ad2; 84 | } 85 | #copyButton:hover + input { 86 | background-color: #1f4369; 87 | border: 2px solid #ffaf23; 88 | } 89 | 90 | #copyButton:active, 91 | #copyButton:active + input { 92 | color: #ffaf23; 93 | } 94 | 95 | dialog#toast { 96 | border-radius: 16px; 97 | bottom: 30px; 98 | box-shadow: 6px 6px 6px rgb(0 0 0 / 40%); 99 | background: #5ee65e; 100 | border: none; 101 | font-weight: 700; 102 | } 103 | -------------------------------------------------------------------------------- /page/public/assets/js/watch.js: -------------------------------------------------------------------------------- 1 | const watchButton = document.querySelector("#watchButton"); 2 | const table = document.querySelector("table"); 3 | const tableTbody = document.querySelector("table tbody"); 4 | const current = []; 5 | const db = firebase.firestore(); 6 | const getArgumentsFormatted = (args) => { 7 | if (Object.keys(args) && Object.keys(args).length === 1) { 8 | return args[0]; 9 | } 10 | return JSON.stringify(args, null, 4); 11 | ß; 12 | }; 13 | const renderTable = (list) => { 14 | const trs = list 15 | .map((doc) => doc.data()) 16 | .sort((a, b) => { 17 | return new Date(b.args.date) - new Date(a.args.date); 18 | }) 19 | .map((data) => { 20 | current.push(data); 21 | const log = data.args; 22 | return ` 23 | 24 | 25 | ${log.date} 26 | 27 | ${log.type} 28 | 29 | 30 |
31 |     ${getArgumentsFormatted(log.arguments)}
32 |     
33 | 34 | 35 |
36 |     ${JSON.stringify(data.info, null, 4)}
37 |     
38 | 39 | 40 | `; 41 | }) 42 | .join(""); 43 | 44 | tableTbody.innerHTML = trs; 45 | }; 46 | 47 | const startWatcher = (id) => { 48 | tableTbody.innerHTML = ``; 49 | table.removeAttribute("hidden"); 50 | db.collection("logger") 51 | .doc(id) 52 | .collection("messages") 53 | .get((doc) => { 54 | renderTable(doc.docs); 55 | }); 56 | 57 | db.collection("logger") 58 | .doc(id) 59 | .collection("messages") 60 | .onSnapshot((doc) => { 61 | renderTable(doc.docs); 62 | }); 63 | }; 64 | 65 | watchButton.addEventListener("click", () => { 66 | if (loggerId.value && loggerId.value.length >= 20) 67 | startWatcher(loggerId.value); 68 | }); 69 | 70 | if (window.location.search) { 71 | const params = new URLSearchParams(window.location.search); 72 | const id = params.get("id"); 73 | if (id) { 74 | loggerId.value = id; 75 | startWatcher(id); 76 | } 77 | } 78 | 79 | window.addEventListener("keypress", ($event) => { 80 | console.log($event); 81 | if ($event.ctrlKey && $event.key === "d") { 82 | const comd = confirm("Want to download the current log as json?"); 83 | if (comd === true) { 84 | const dataStr = 85 | "data:text/json;charset=utf-8," + 86 | encodeURIComponent(JSON.stringify(current)); 87 | const dlAnchorElem = document.createElement("a"); 88 | dlAnchorElem.setAttribute("href", dataStr); 89 | dlAnchorElem.setAttribute("download", `remote-logger-${loggerId.valueß}.json`); 90 | dlAnchorElem.click(); 91 | } 92 | } 93 | }); 94 | -------------------------------------------------------------------------------- /page/public/assets/js/components.js: -------------------------------------------------------------------------------- 1 | class RLHeader extends HTMLElement { 2 | constructor() { 3 | // Sempre chame super primeiro no construtor 4 | super(); 5 | this.attachShadow({ mode: "open" }); 6 | const wrapper = document.createElement("header"); 7 | 8 | const title = wrapper.appendChild(document.createElement("h1")); 9 | title.textContent = "Remote Logger"; 10 | 11 | const icon = document.createElement("span"); 12 | icon.innerHTML = ` `; 15 | const githubLink = wrapper.appendChild(document.createElement("a")); 16 | githubLink.href = "https://github.com/schirrel/remote-logger"; 17 | githubLink.appendChild(icon); 18 | 19 | const style = document.createElement("style"); 20 | style.textContent = ` 21 | header { 22 | height: 50px; 23 | display: flex; 24 | align-items: center; 25 | background: #ffaf23; 26 | display: flex; 27 | padding: 0 8px; 28 | justify-content: space-between 29 | } 30 | h1 { 31 | font-weight: bold; 32 | letter-spacing: 8px; 33 | margin: 0; 34 | padding: 0 35 | } 36 | svg { 37 | transition: fill 400ms ease-in-out; 38 | will-change: color; 39 | } 40 | a:hover svg { 41 | fill: white 42 | }`; 43 | this.shadowRoot.append(style, wrapper); 44 | } 45 | } 46 | 47 | class RLLink extends HTMLElement { 48 | constructor() { 49 | // Sempre chame super primeiro no construtor 50 | super(); 51 | } 52 | 53 | connectedCallback() { 54 | this.attachShadow({ mode: "open" }); 55 | const style = document.createElement("style"); 56 | const anchorLink = document.createElement("a"); 57 | anchorLink.href = this.getAttribute("href"); 58 | anchorLink.innerHTML = ""; 59 | style.textContent = ` 60 | a { 61 | color: white; 62 | border-bottom: 1px solid #ffaf23; 63 | text-decoration: none; 64 | padding: 0 4px; 65 | transition: color 300ms ease-in-out, border 300ms ease-in-out, 66 | padding 300ms ease-in-out; 67 | will-change: color, border, padding; 68 | } 69 | 70 | a:hover { 71 | color: #ffaf23; 72 | border-color: white; 73 | padding: 0 10px; 74 | } 75 | `; 76 | this.shadowRoot.append(style, anchorLink); 77 | } 78 | } 79 | class RLFooter extends HTMLElement { 80 | constructor() { 81 | // Sempre chame super primeiro no construtor 82 | super(); 83 | } 84 | 85 | connectedCallback() { 86 | this.attachShadow({ mode: "open" }); 87 | const wrapper = document.createElement("footer"); 88 | 89 | const howToUseLink = document.createElement("rl-link"); 90 | 91 | howToUseLink.setAttribute( 92 | "href", 93 | "https://github.com/schirrel/remote-logger/tree/main#usage" 94 | ); 95 | howToUseLink.textContent = "Docs"; 96 | wrapper.appendChild(howToUseLink); 97 | 98 | const sdkLink = document.createElement("rl-link"); 99 | sdkLink.setAttribute( 100 | "href", 101 | "https://github.com/schirrel/remote-logger/blob/main/lib/remote-logger.min.js" 102 | ); 103 | sdkLink.textContent = "Download SDK"; 104 | wrapper.appendChild(sdkLink); 105 | 106 | if (this.hasAttribute("show-new-log")) { 107 | const newLogLink = document.createElement("rl-link"); 108 | newLogLink.href = ""; 109 | sdkLink.setAttribute("href", "https://remote-logger.web.app"); 110 | newLogLink.textContent = "Generate New Id"; 111 | wrapper.appendChild(newLogLink); 112 | } 113 | const style = document.createElement("style"); 114 | style.textContent = ` 115 | footer { 116 | height: 50px; 117 | display: flex; 118 | align-items: center; 119 | background: #1f4369; 120 | justify-content: center; 121 | gap: 16px 122 | } 123 | `; 124 | this.shadowRoot.append(style, wrapper); 125 | } 126 | } 127 | 128 | customElements.define("rl-footer", RLFooter); 129 | customElements.define("rl-header", RLHeader); 130 | customElements.define("rl-link", RLLink); 131 | --------------------------------------------------------------------------------