├── 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 | | Date |
24 | Log Type |
25 | Arguments |
26 | System Info |
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 |
32 |
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 | 
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 |
--------------------------------------------------------------------------------