1
0
mirror of https://github.com/taigrr/log-socket synced 2025-01-18 04:53:14 -08:00

19 Commits

Author SHA1 Message Date
147a8cb30b Merge pull request #10 from ethanholz/README
Added a README
2022-10-10 13:38:59 -07:00
Ethan Holz
8e044e3993 docs: Updated README to include running example. 2022-10-10 12:16:52 -05:00
Ethan Holz
e97d37012e docs: Added inital README 2022-10-10 12:09:29 -05:00
ebef59a9a8 actually set value for depth 2021-08-23 21:58:05 -07:00
b3ceb12277 fixes fileinfo reflection issue 2021-08-23 21:45:08 -07:00
45cad34fdc fixes fileinfo reflection issue 2021-08-23 21:44:56 -07:00
f0c16a0c56 fixes fileinfo reflection issue 2021-08-23 21:36:49 -07:00
c18854598d Added a logger type 2021-08-23 21:25:01 -07:00
63ed3a2ad7 logger => log 2021-08-23 21:12:21 -07:00
f51ec53a89 Adds Notice logging level to support NATS 2021-08-23 20:58:15 -07:00
d21c91379e update go compiler target version 2021-08-17 10:07:07 -07:00
af2116af48 add sponsorship 2021-07-05 21:03:17 -07:00
b65a10e7a8 detect tls presence based on incoming request for websocket 2021-07-05 16:10:50 -07:00
a07d3a11df minor cleanup, adds support for formatted prints 2021-07-03 08:45:19 -07:00
5f18def7fa Adds license 2021-04-09 11:09:18 -07:00
2c7cf0494e Adds autoscroll feature to browser 2021-04-09 10:39:23 -07:00
b744f083dc general refactoring 2021-04-09 10:05:59 -07:00
1ea76f4ab1 breaks browser out into package 2021-04-09 09:47:49 -07:00
2e01f23b30 Allow setting of stderr level 2021-04-09 09:40:25 -07:00
11 changed files with 658 additions and 218 deletions

12
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# These are supported funding model platforms
github: taigrr # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

12
LICENSE Normal file
View File

@@ -0,0 +1,12 @@
Copyright (C) 2019-2021 by Tai Groot <tai@taigrr.com>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

15
README.md Normal file
View File

@@ -0,0 +1,15 @@
log-socket
==========
`log-socket` is a drop-in replacement for Go's `log` package that allows for streaming of logs via WebSockets.
## Installation
To install the library:
`go get github.com/taigrr/log-socket`
## Running
To run a demo of this library:
`go run main.go`
This demo will do a sample of every log type and push results to `0.0.0.0:8080`. Once running, you can open a browser and navigate to
`0.0.0.0:8080` to see an example implementation of how logs are streamed.

217
browser/browser.go Normal file
View File

@@ -0,0 +1,217 @@
package browser
import (
"html/template"
"net/http"
"strings"
)
func LogSocketViewHandler(w http.ResponseWriter, r *http.Request) {
wsResource := r.Host + r.URL.Path
if r.TLS != nil {
wsResource = "wss://" + wsResource
} else {
wsResource = "ws://" + wsResource
}
wsResource = strings.TrimSuffix(wsResource, "/") + "/ws"
homeTemplate.Execute(w, wsResource)
}
var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<center>
<input type="text" id="search" onkeyup="filterTable()" placeholder="Filter...">
<input type="checkbox" id="shouldScroll" checked>Enable Autoscroll<br>
<table id="logHeaders" style="text-align:left; width:80%;" >
<tbody>
<tr class="header">
<th style="width:20%;">TimeStamp</th>
<th style="width:5%;">Level</th>
<th style="width:65%;">Output</th>
<th style="width:10%;">Source</th>
</tr>
</tbody>
</table>
<div id="tableWrapper">
<table id="logs" style="text-align:left; width:100%;" >
<tbody id="tbodylogs">
<tr class="header">
<th style="width:20%;"></th>
<th style="width:5%;"></th>
<th style="width:65%;"></th>
<th style="width:10%;"></th>
</tr>
</tbody>
</table>
</div>
<br>
<input class="button" type="button" id="download" value="Download Logs" style="background-color:#3f51b5;"/>
<input class="button" type="button" id="delete" value="Delete Logs" style="background-color:#f44336"/>
</center>
</body>
<footer>
<style>
#tableWrapper{
overflow-y: scroll;
display: flow-root;
width: 80%;
height: 80vh;
}
td,tr{
height: min-content;
}
.button{
display: inline-block;
width: 5vw;
height: 5vh;
}
</style>
<script>
var logTable = document.getElementById("logs");
var logTableB = document.getElementById("tbodylogs");
var ws = null;
var application = "demo-commit"
var logs = [];
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function download(filename, text) {
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
function openSocket() {
if (ws) {
return false;
}
ws = new WebSocket("{{.}}");
ws.onclose = async function(evt) {
ws = null;
while(ws == null){
openSocket()
await sleep(5000);
}
}
ws.onmessage = function(evt) {
var entry = JSON.parse(evt.data)
logs.push(entry)
var row = document.createElement('tr');
var ts = document.createElement('td');
var tst = document.createTextNode(entry.timestamp);
ts.appendChild(tst);
row.appendChild(ts);
var ts = document.createElement('td');
var tst = document.createTextNode(entry.level);
ts.appendChild(tst);
row.appendChild(ts);
var ts = document.createElement('td');
var tst = document.createTextNode(entry.output);
ts.appendChild(tst);
row.appendChild(ts);
var ts = document.createElement('td');
var tst = document.createTextNode(entry.file);
ts.appendChild(tst);
row.appendChild(ts);
var bg="";
switch(entry.level){
case "INFO":
bg="white";
break;
case "ERROR":
bg="#f44336";
break;
case "WARN":
bg="#fb8c00"
break;
case "TRACE":
bg="#E1F5FE"
break;
case "DEBUG":
bg="#B3E5FC"
break;
default:
bg="white"
break;
}
row.style.backgroundColor=bg
logTableB.append(row)
filterTable()
}
ws.onerror = function(evt) {
if (evt != null && evt.data != null){
// handle error here
}
}
}
function clearTable(){
if(!window.confirm("Are you sure you want to delete all logs?")){
return
}
logs = []
while (logTableB.childNodes.length > 1) {
logTableB.removeChild(logTableB.childNodes[1]);
}
}
function filterTable() {
var cols, input, filter, table, tr, td, i, txtValue, w;
input = document.getElementById("search");
filter = input.value;
table = logTableB;
tr = table.getElementsByTagName("tr");
for (i = 1; i < tr.length; i++) {
cols = tr[i].getElementsByTagName("td");
var visible = false;
for (w = 0; w < cols.length; w++){
if (!visible && cols[w]) {
td = cols[w]
txtValue = td.textContent || td.innerText;
if (txtValue.indexOf(filter) > -1) {
visible = true
}
}
}
if(visible){
tr[i].style.display = "";
} else {
tr[i].style.display = "none";
}
}
}
function pageScroll() {
if (document.getElementById('shouldScroll').checked) {
document.getElementById('tableWrapper').scrollBy(0,10);
}
setTimeout(pageScroll,10);
}
document.getElementById("delete").addEventListener("click", function(){
clearTable()
}, false);
document.getElementById("download").addEventListener("click", function(){
download(application+'.json',JSON.stringify(logs));
}, false);
openSocket();
pageScroll();
</script>
</footer>
</html>
`))

2
go.mod
View File

@@ -1,5 +1,5 @@
module github.com/taigrr/log-socket module github.com/taigrr/log-socket
go 1.16 go 1.17
require github.com/gorilla/websocket v1.4.2 require github.com/gorilla/websocket v1.4.2

View File

@@ -1,4 +1,4 @@
package logger package log
import ( import (
"errors" "errors"
@@ -10,16 +10,6 @@ import (
"time" "time"
) )
const (
LTrace Level = iota
LDebug
LInfo
LWarn
LError
LPanic
LFatal
)
var ( var (
clients []*Client clients []*Client
sliceTex sync.Mutex sliceTex sync.Mutex
@@ -112,6 +102,10 @@ func createLog(e Entry) {
sliceTex.Unlock() sliceTex.Unlock()
} }
func SetLogLevel(level Level) {
stderrClient.LogLevel = level
}
// SetLogLevel set log level of logger // SetLogLevel set log level of logger
func (c *Client) SetLogLevel(level Level) { func (c *Client) SetLogLevel(level Level) {
if !c.initialized { if !c.initialized {
@@ -138,9 +132,19 @@ func Trace(args ...interface{}) {
level: LTrace, level: LTrace,
} }
createLog(e) createLog(e)
// entry := logger.WithFields(logrus.Fields{}) }
// entry.Data["file"] = fileInfo(2)
// entry.Debug(args...) // Formatted print for Trace
func Tracef(format string, args ...interface{}) {
output := fmt.Sprintf(format, args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(2),
Level: "TRACE",
level: LTrace,
}
createLog(e)
} }
// Debug prints out logs on debug level // Debug prints out logs on debug level
@@ -154,7 +158,19 @@ func Debug(args ...interface{}) {
level: LDebug, level: LDebug,
} }
createLog(e) createLog(e)
}
// Formatted print for Debug
func Debugf(format string, args ...interface{}) {
output := fmt.Sprintf(format, args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(2),
Level: "DEBUG",
level: LDebug,
}
createLog(e)
} }
// Info prints out logs on info level // Info prints out logs on info level
@@ -170,6 +186,45 @@ func Info(args ...interface{}) {
createLog(e) createLog(e)
} }
// Formatted print for Info
func Infof(format string, args ...interface{}) {
output := fmt.Sprintf(format, args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(2),
Level: "INFO",
level: LInfo,
}
createLog(e)
}
// Info prints out logs on info level
func Notice(args ...interface{}) {
output := fmt.Sprint(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(2),
Level: "NOTICE",
level: LNotice,
}
createLog(e)
}
// Formatted print for Info
func Noticef(format string, args ...interface{}) {
output := fmt.Sprintf(format, args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(2),
Level: "NOTICE",
level: LNotice,
}
createLog(e)
}
// Warn prints out logs on warn level // Warn prints out logs on warn level
func Warn(args ...interface{}) { func Warn(args ...interface{}) {
output := fmt.Sprint(args...) output := fmt.Sprint(args...)
@@ -183,6 +238,19 @@ func Warn(args ...interface{}) {
createLog(e) createLog(e)
} }
// Formatted print for Warn
func Warnf(format string, args ...interface{}) {
output := fmt.Sprintf(format, args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(2),
Level: "WARN",
level: LWarn,
}
createLog(e)
}
// Error prints out logs on error level // Error prints out logs on error level
func Error(args ...interface{}) { func Error(args ...interface{}) {
output := fmt.Sprint(args...) output := fmt.Sprint(args...)
@@ -196,6 +264,19 @@ func Error(args ...interface{}) {
createLog(e) createLog(e)
} }
// Formatted print for error
func Errorf(format string, args ...interface{}) {
output := fmt.Sprintf(format, args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(2),
Level: "ERROR",
level: LError,
}
createLog(e)
}
// Panic prints out logs on panic level // Panic prints out logs on panic level
func Panic(args ...interface{}) { func Panic(args ...interface{}) {
output := fmt.Sprint(args...) output := fmt.Sprint(args...)
@@ -219,6 +300,29 @@ func Panic(args ...interface{}) {
panic(errors.New(output)) panic(errors.New(output))
} }
// Formatted print for panic
func Panicf(format string, args ...interface{}) {
output := fmt.Sprintf(format, args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(2),
Level: "PANIC",
level: LPanic,
}
createLog(e)
if len(args) >= 0 {
switch args[0].(type) {
case error:
panic(args[0])
default:
// falls through to default below
}
}
Flush()
panic(errors.New(output))
}
// Fatal prints out logs on fatal level // Fatal prints out logs on fatal level
func Fatal(args ...interface{}) { func Fatal(args ...interface{}) {
output := fmt.Sprint(args...) output := fmt.Sprint(args...)
@@ -234,6 +338,21 @@ func Fatal(args ...interface{}) {
os.Exit(1) os.Exit(1)
} }
// Formatted print for fatal
func Fatalf(format string, args ...interface{}) {
output := fmt.Sprintf(format, args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(2),
Level: "FATAL",
level: LFatal,
}
createLog(e)
Flush()
os.Exit(1)
}
// fileInfo for getting which line in which file // fileInfo for getting which line in which file
func fileInfo(skip int) string { func fileInfo(skip int) string {
_, file, line, ok := runtime.Caller(skip) _, file, line, ok := runtime.Caller(skip)

View File

@@ -1,4 +1,4 @@
package logger package log
import ( import (
"strconv" "strconv"

244
log/logger.go Normal file
View File

@@ -0,0 +1,244 @@
package log
import (
"errors"
"fmt"
"os"
"time"
)
func (l *Logger) SetInfoDepth(depth int) {
l.FileInfoDepth = depth
}
// Trace prints out logs on trace level
func (l Logger) Trace(args ...interface{}) {
output := fmt.Sprint(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(l.FileInfoDepth),
Level: "TRACE",
level: LTrace,
}
createLog(e)
}
// Formatted print for Trace
func (l Logger) Tracef(format string, args ...interface{}) {
output := fmt.Sprintf(format, args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(l.FileInfoDepth),
Level: "TRACE",
level: LTrace,
}
createLog(e)
}
// Debug prints out logs on debug level
func (l Logger) Debug(args ...interface{}) {
output := fmt.Sprint(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(l.FileInfoDepth),
Level: "DEBUG",
level: LDebug,
}
createLog(e)
}
// Formatted print for Debug
func (l Logger) Debugf(format string, args ...interface{}) {
output := fmt.Sprintf(format, args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(l.FileInfoDepth),
Level: "DEBUG",
level: LDebug,
}
createLog(e)
}
// Info prints out logs on info level
func (l Logger) Info(args ...interface{}) {
output := fmt.Sprint(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(l.FileInfoDepth),
Level: "INFO",
level: LInfo,
}
createLog(e)
}
// Formatted print for Info
func (l Logger) Infof(format string, args ...interface{}) {
output := fmt.Sprintf(format, args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(l.FileInfoDepth),
Level: "INFO",
level: LInfo,
}
createLog(e)
}
// Info prints out logs on info level
func (l Logger) Notice(args ...interface{}) {
output := fmt.Sprint(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(l.FileInfoDepth),
Level: "NOTICE",
level: LNotice,
}
createLog(e)
}
// Formatted print for Info
func (l Logger) Noticef(format string, args ...interface{}) {
output := fmt.Sprintf(format, args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(l.FileInfoDepth),
Level: "NOTICE",
level: LNotice,
}
createLog(e)
}
// Warn prints out logs on warn level
func (l Logger) Warn(args ...interface{}) {
output := fmt.Sprint(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(l.FileInfoDepth),
Level: "WARN",
level: LWarn,
}
createLog(e)
}
// Formatted print for Warn
func (l Logger) Warnf(format string, args ...interface{}) {
output := fmt.Sprintf(format, args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(l.FileInfoDepth),
Level: "WARN",
level: LWarn,
}
createLog(e)
}
// Error prints out logs on error level
func (l Logger) Error(args ...interface{}) {
output := fmt.Sprint(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(l.FileInfoDepth),
Level: "ERROR",
level: LError,
}
createLog(e)
}
// Formatted print for error
func (l Logger) Errorf(format string, args ...interface{}) {
output := fmt.Sprintf(format, args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(l.FileInfoDepth),
Level: "ERROR",
level: LError,
}
createLog(e)
}
// Panic prints out logs on panic level
func (l Logger) Panic(args ...interface{}) {
output := fmt.Sprint(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(l.FileInfoDepth),
Level: "PANIC",
level: LPanic,
}
createLog(e)
if len(args) >= 0 {
switch args[0].(type) {
case error:
panic(args[0])
default:
// falls through to default below
}
}
Flush()
panic(errors.New(output))
}
// Formatted print for panic
func (l Logger) Panicf(format string, args ...interface{}) {
output := fmt.Sprintf(format, args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(l.FileInfoDepth),
Level: "PANIC",
level: LPanic,
}
createLog(e)
if len(args) >= 0 {
switch args[0].(type) {
case error:
panic(args[0])
default:
// falls through to default below
}
}
Flush()
panic(errors.New(output))
}
// Fatal prints out logs on fatal level
func (l Logger) Fatal(args ...interface{}) {
output := fmt.Sprint(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(l.FileInfoDepth),
Level: "FATAL",
level: LFatal,
}
createLog(e)
Flush()
os.Exit(1)
}
// Formatted print for fatal
func (l Logger) Fatalf(format string, args ...interface{}) {
output := fmt.Sprintf(format, args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(l.FileInfoDepth),
Level: "FATAL",
level: LFatal,
}
createLog(e)
Flush()
os.Exit(1)
}

View File

@@ -1,10 +1,21 @@
package logger package log
import "time" import "time"
type LogWriter chan Entry type LogWriter chan Entry
type Level int type Level int
const (
LTrace Level = iota
LDebug
LInfo
LNotice
LWarn
LError
LPanic
LFatal
)
type Client struct { type Client struct {
LogLevel Level `json:"level"` LogLevel Level `json:"level"`
writer LogWriter writer LogWriter
@@ -18,3 +29,7 @@ type Entry struct {
Level string `json:"level"` Level string `json:"level"`
level Level level Level
} }
type Logger struct {
FileInfoDepth int
}

206
main.go
View File

@@ -2,21 +2,16 @@ package main
import ( import (
"flag" "flag"
"html/template"
"log"
"net/http" "net/http"
"time" "time"
"github.com/taigrr/log-socket/logger" "github.com/taigrr/log-socket/browser"
logger "github.com/taigrr/log-socket/log"
"github.com/taigrr/log-socket/ws" "github.com/taigrr/log-socket/ws"
) )
var addr = flag.String("addr", "0.0.0.0:8080", "http service address") var addr = flag.String("addr", "0.0.0.0:8080", "http service address")
func home(w http.ResponseWriter, r *http.Request) {
homeTemplate.Execute(w, "ws://"+r.Host+"/logs")
}
func generateLogs() { func generateLogs() {
for { for {
logger.Info("This is an info log!") logger.Info("This is an info log!")
@@ -24,204 +19,15 @@ func generateLogs() {
logger.Debug("This is a debug log!") logger.Debug("This is a debug log!")
logger.Warn("This is a warn log!") logger.Warn("This is a warn log!")
logger.Error("This is an error log!") logger.Error("This is an error log!")
time.Sleep(10 * time.Second) time.Sleep(2 * time.Second)
} }
} }
func main() { func main() {
defer logger.Flush() defer logger.Flush()
flag.Parse() flag.Parse()
http.HandleFunc("/logs", ws.LogSocketHandler) http.HandleFunc("/ws", ws.LogSocketHandler)
http.HandleFunc("/", home) http.HandleFunc("/", browser.LogSocketViewHandler)
go generateLogs() go generateLogs()
log.Fatal(http.ListenAndServe(*addr, nil)) logger.Fatal(http.ListenAndServe(*addr, nil))
} }
var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<center>
<input type="text" id="search" onkeyup="filterTable()" placeholder="Filter...">
<table id="logHeaders" style="text-align:left; width:80%;" >
<tbody>
<tr class="header">
<th style="width:20%;">TimeStamp</th>
<th style="width:5%;">Level</th>
<th style="width:65%;">Output</th>
<th style="width:10%;">Source</th>
</tr>
</tbody>
</table>
<div id="tableWrapper">
<table id="logs" style="text-align:left; width:100%;" >
<tbody id="tbodylogs">
<tr class="header">
<th style="width:20%;"></th>
<th style="width:5%;"></th>
<th style="width:65%;"></th>
<th style="width:10%;"></th>
</tr>
</tbody>
</table>
</div>
<br>
<input class="button" type="button" id="download" value="Download Logs" style="background-color:#3f51b5;"/>
<input class="button" type="button" id="delete" value="Delete Logs" style="background-color:#f44336"/>
</center>
</body>
<footer>
<style>
#tableWrapper{
overflow-y: scroll;
display: flow-root;
width: 80%;
height: 80vh;
}
td,tr{
height: min-content;
}
.button{
display: inline-block;
width: 5vw;
height: 5vh;
}
</style>
<script>
var logTable = document.getElementById("logs");
var logTableB = document.getElementById("tbodylogs");
var ws = null;
var application = "demo-commit"
var logs = [];
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function download(filename, text) {
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
function openSocket() {
if (ws) {
return false;
}
ws = new WebSocket("{{.}}");
ws.onclose = async function(evt) {
ws = null;
while(ws == null){
openSocket()
await sleep(5000);
}
}
ws.onmessage = function(evt) {
var entry = JSON.parse(evt.data)
logs.push(entry)
var row = document.createElement('tr');
var ts = document.createElement('td');
var tst = document.createTextNode(entry.timestamp);
ts.appendChild(tst);
row.appendChild(ts);
var ts = document.createElement('td');
var tst = document.createTextNode(entry.level);
ts.appendChild(tst);
row.appendChild(ts);
var ts = document.createElement('td');
var tst = document.createTextNode(entry.output);
ts.appendChild(tst);
row.appendChild(ts);
var ts = document.createElement('td');
var tst = document.createTextNode(entry.file);
ts.appendChild(tst);
row.appendChild(ts);
var bg="";
switch(entry.level){
case "INFO":
bg="white";
break;
case "ERROR":
bg="#f44336";
break;
case "WARN":
bg="#fb8c00"
break;
case "TRACE":
bg="#E1F5FE"
break;
case "DEBUG":
bg="#B3E5FC"
break;
default:
bg="white"
break;
}
row.style.backgroundColor=bg
logTableB.append(row)
filterTable()
}
ws.onerror = function(evt) {
if (evt != null && evt.data != null){
// handle error here
}
}
}
function clearTable(){
if(!window.confirm("Are you sure you want to delete all logs?")){
return
}
logs = []
while (logTableB.childNodes.length > 1) {
logTableB.removeChild(logTableB.childNodes[1]);
}
}
function filterTable() {
var cols, input, filter, table, tr, td, i, txtValue, w;
input = document.getElementById("search");
filter = input.value;
table = logTableB;
tr = table.getElementsByTagName("tr");
for (i = 1; i < tr.length; i++) {
cols = tr[i].getElementsByTagName("td");
var visible = false;
for (w = 0; w < cols.length; w++){
if (!visible && cols[w]) {
td = cols[w]
txtValue = td.textContent || td.innerText;
if (txtValue.indexOf(filter) > -1) {
visible = true
}
}
}
if(visible){
tr[i].style.display = "";
} else {
tr[i].style.display = "none";
}
}
}
document.getElementById("delete").addEventListener("click", function(){
clearTable()
}, false);
document.getElementById("download").addEventListener("click", function(){
download(application+'.json',JSON.stringify(logs));
}, false);
openSocket();
</script>
</footer>
</html>
`))

View File

@@ -5,7 +5,7 @@ import (
"net/http" "net/http"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/taigrr/log-socket/logger" logger "github.com/taigrr/log-socket/log"
) )
// var addr = flag.String("addr", "localhost:8080", "http service address") // var addr = flag.String("addr", "localhost:8080", "http service address")