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

Compare commits

..

No commits in common. "master" and "v0.1.1" have entirely different histories.

15 changed files with 464 additions and 1178 deletions

12
.github/FUNDING.yml vendored
View File

@ -1,12 +0,0 @@
# 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']

View File

@ -1,24 +0,0 @@
name: Go package
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.16'
- name: Install dependencies
run: go get .
- name: Build
run: go build -v ./...
- name: Test
run: go test -v ./...

12
LICENSE
View File

@ -1,12 +0,0 @@
Copyright (C) 2019-2022 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.

View File

@ -1,15 +0,0 @@
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.

View File

@ -1,24 +0,0 @@
package browser
import (
_ "embed"
"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)
}
//go:embed viewer.html
var webpage string
var homeTemplate = template.Must(template.New("").Parse(webpage))

View File

@ -1,196 +0,0 @@
<!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>

4
go.mod
View File

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

4
go.sum
View File

@ -1,2 +1,2 @@
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=

View File

@ -1,489 +0,0 @@
package log
import (
"errors"
"fmt"
"os"
"runtime"
"strings"
"sync"
"time"
)
var (
clients []*Client
sliceTex sync.Mutex
stderrClient *Client
cleanup sync.Once
stderrFinished chan bool
)
func init() {
stderrClient = CreateClient()
stderrClient.SetLogLevel(LTrace)
stderrFinished = make(chan bool, 1)
go stderrClient.logStdErr()
}
func (c *Client) logStdErr() {
for e := range c.writer {
if e.level >= c.LogLevel {
fmt.Fprintf(os.Stderr, "%s\t%s\t%s\t%s\n", e.Timestamp.String(), e.Level, e.Output, e.File)
}
}
stderrFinished <- true
}
func CreateClient() *Client {
var client Client
client.initialized = true
client.writer = make(LogWriter, 1000)
sliceTex.Lock()
clients = append(clients, &client)
sliceTex.Unlock()
return &client
}
func Flush() {
cleanup.Do(func() {
close(stderrClient.writer)
<-stderrFinished
stderrClient.Destroy()
})
}
func (c *Client) Destroy() error {
var otherClients []*Client
if !c.initialized {
panic(errors.New("cannot delete uninitialized client, did you use CreateClient?"))
}
sliceTex.Lock()
c.writer = nil
c.initialized = false
for _, x := range clients {
if x.initialized {
otherClients = append(otherClients, x)
}
}
clients = otherClients
sliceTex.Unlock()
return nil
}
func (c *Client) GetLogLevel() Level {
if !c.initialized {
panic(errors.New("cannot get level for uninitialized client, use CreateClient instead"))
}
return c.LogLevel
}
func createLog(e Entry) {
sliceTex.Lock()
for _, c := range clients {
func(c *Client, e Entry) {
select {
case c.writer <- e:
// try to clear out one of the older entries
default:
select {
case <-c.writer:
c.writer <- e
default:
}
}
}(c, e)
}
sliceTex.Unlock()
}
func SetLogLevel(level Level) {
stderrClient.LogLevel = level
}
// SetLogLevel set log level of logger
func (c *Client) SetLogLevel(level Level) {
if !c.initialized {
panic(errors.New("cannot set level for uninitialized client, use CreateClient instead"))
}
c.LogLevel = level
}
func (c *Client) Get() Entry {
if !c.initialized {
panic(errors.New("cannot get logs for uninitialized client, did you use CreateClient?"))
}
return <-c.writer
}
// Trace prints out logs on trace level
func Trace(args ...interface{}) {
output := fmt.Sprint(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(2),
Level: "TRACE",
level: LTrace,
}
createLog(e)
}
// 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)
}
// Trace prints out logs on trace level with newline
func Traceln(args ...interface{}) {
output := fmt.Sprintln(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(2),
Level: "TRACE",
level: LTrace,
}
createLog(e)
}
// Debug prints out logs on debug level
func Debug(args ...interface{}) {
output := fmt.Sprint(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(2),
Level: "DEBUG",
level: LDebug,
}
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)
}
// Debug prints out logs on debug level with a newline
func Debugln(args ...interface{}) {
output := fmt.Sprintln(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(2),
Level: "DEBUG",
level: LDebug,
}
createLog(e)
}
// Info prints out logs on info level
func Info(args ...interface{}) {
output := fmt.Sprint(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(2),
Level: "INFO",
level: LInfo,
}
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 with a newline
func Infoln(args ...interface{}) {
output := fmt.Sprintln(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)
}
// Info prints out logs on info level with a newline
func Noticeln(args ...interface{}) {
output := fmt.Sprintln(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(2),
Level: "NOTICE",
level: LNotice,
}
createLog(e)
}
// Warn prints out logs on warn level
func Warn(args ...interface{}) {
output := fmt.Sprint(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(2),
Level: "WARN",
level: LWarn,
}
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)
}
// Newline print for Warn
func Warnln(args ...interface{}) {
output := fmt.Sprintln(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(2),
Level: "WARN",
level: LWarn,
}
createLog(e)
}
// Error prints out logs on error level
func Error(args ...interface{}) {
output := fmt.Sprint(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(2),
Level: "ERROR",
level: LError,
}
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)
}
// Error prints out logs on error level with a newline
func Errorln(args ...interface{}) {
output := fmt.Sprintln(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(2),
Level: "ERROR",
level: LError,
}
createLog(e)
}
// Panic prints out logs on panic level
func Panic(args ...interface{}) {
output := fmt.Sprint(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))
}
// 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))
}
func Panicln(args ...interface{}) {
output := fmt.Sprintln(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
func Fatal(args ...interface{}) {
output := fmt.Sprint(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(2),
Level: "FATAL",
level: LFatal,
}
createLog(e)
Flush()
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)
}
func Fatalln(args ...interface{}) {
output := fmt.Sprintln(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(2),
Level: "FATAL",
level: LFatal,
}
createLog(e)
Flush()
os.Exit(1)
}
func Print(args ...interface{}) {
Info(args...)
}
func Printf(format string, args ...interface{}) {
Infof(format, args...)
}
func Println(args ...interface{}) {
Infoln(args...)
}
// fileInfo for getting which line in which file
func fileInfo(skip int) string {
_, file, line, ok := runtime.Caller(skip)
if !ok {
file = "<???>"
line = 1
} else {
slash := strings.LastIndex(file, "/")
if slash >= 0 {
file = file[slash+1:]
}
}
return fmt.Sprintf("%s:%d", file, line)
}

View File

@ -1,366 +0,0 @@
package log
import (
"errors"
"fmt"
"os"
"time"
)
func Default() *Logger {
return &Logger{FileInfoDepth: 0}
}
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)
}
// Trace prints out logs on trace level with newline
func (l Logger) Traceln(args ...interface{}) {
output := fmt.Sprintln(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 with newline
func (l Logger) Infoln(args ...interface{}) {
output := fmt.Sprintln(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(l.FileInfoDepth),
Level: "INFO",
level: LInfo,
}
createLog(e)
}
// Notice prints out logs on notice 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 Notice
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)
}
// Notice prints out logs on notice level with newline
func (l Logger) Noticeln(args ...interface{}) {
output := fmt.Sprintln(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)
}
// Warn prints out logs on warn level with a newline
func (l Logger) Warnln(args ...interface{}) {
output := fmt.Sprintln(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)
}
// Error prints out logs on error level with a new line
func (l Logger) Errorln(args ...interface{}) {
output := fmt.Sprintln(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))
}
// Panic prints out logs on panic level with a newline
func (l Logger) Panicln(args ...interface{}) {
output := fmt.Sprintln(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)
}
// Fatal prints fatal level with a new line
func (l Logger) Fatalln(args ...interface{}) {
output := fmt.Sprintln(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(l.FileInfoDepth),
Level: "FATAL",
level: LFatal,
}
createLog(e)
Flush()
os.Exit(1)
}
// Handles print to info
func (l Logger) Print(args ...interface{}) {
l.Info(args...)
}
// Handles formatted print to info
func (l Logger) Printf(format string, args ...interface{}) {
l.Infof(format, args...)
}
// Handles print to info with new line
func (l Logger) Println(args ...interface{}) {
l.Infoln(args...)
}

254
logger/logger.go Normal file
View File

@ -0,0 +1,254 @@
package logger
import (
"errors"
"fmt"
"os"
"runtime"
"strings"
"sync"
"time"
)
const (
LTrace Level = iota
LDebug
LInfo
LWarn
LError
LPanic
LFatal
)
var (
clients []*Client
sliceTex sync.Mutex
stderrClient *Client
cleanup sync.Once
stderrFinished chan bool
)
func init() {
stderrClient = CreateClient()
stderrClient.SetLogLevel(LTrace)
stderrFinished = make(chan bool, 1)
go stderrClient.logStdErr()
}
func (c *Client) logStdErr() {
for {
select {
case e, more := <-c.writer:
if e.level >= c.LogLevel {
fmt.Fprintf(os.Stderr, "%s\t%s\t%s\t%s\n", e.Timestamp.String(), e.Level, e.Output, e.File)
}
if !more {
stderrFinished <- true
return
}
}
}
}
func CreateClient() *Client {
var client Client
client.initialized = true
client.writer = make(LogWriter, 1000)
sliceTex.Lock()
clients = append(clients, &client)
sliceTex.Unlock()
return &client
}
func Flush() {
cleanup.Do(func() {
close(stderrClient.writer)
<-stderrFinished
stderrClient.Destroy()
})
}
func (c *Client) Destroy() error {
var otherClients []*Client
if !c.initialized {
panic(errors.New("Cannot delete uninitialized client, did you use CreateClient?"))
}
sliceTex.Lock()
c.writer = nil
c.initialized = false
for _, x := range clients {
if x.initialized {
otherClients = append(otherClients, x)
}
}
clients = otherClients
sliceTex.Unlock()
return nil
}
func (c *Client) GetLogLevel() Level {
if !c.initialized {
panic(errors.New("Cannot get level for uninitialized client, use CreateClient instead"))
}
return c.LogLevel
}
func createLog(e Entry) {
sliceTex.Lock()
for _, c := range clients {
func(c *Client, e Entry) {
select {
case c.writer <- e:
// try to clear out one of the older entries
default:
select {
case <-c.writer:
c.writer <- e
default:
}
}
}(c, e)
}
sliceTex.Unlock()
}
func SetLogLevel(level Level) {
stderrClient.LogLevel = level
}
// SetLogLevel set log level of logger
func (c *Client) SetLogLevel(level Level) {
if !c.initialized {
panic(errors.New("Cannot set level for uninitialized client, use CreateClient instead"))
}
c.LogLevel = level
}
func (c *Client) Get() Entry {
if !c.initialized {
panic(errors.New("Cannot get logs for uninitialized client, did you use CreateClient?"))
}
return <-c.writer
}
// Trace prints out logs on trace level
func Trace(args ...interface{}) {
output := fmt.Sprint(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(2),
Level: "TRACE",
level: LTrace,
}
createLog(e)
// entry := logger.WithFields(logrus.Fields{})
// entry.Data["file"] = fileInfo(2)
// entry.Debug(args...)
}
// Debug prints out logs on debug level
func Debug(args ...interface{}) {
output := fmt.Sprint(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(2),
Level: "DEBUG",
level: LDebug,
}
createLog(e)
}
// Info prints out logs on info level
func Info(args ...interface{}) {
output := fmt.Sprint(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(2),
Level: "INFO",
level: LInfo,
}
createLog(e)
}
// Warn prints out logs on warn level
func Warn(args ...interface{}) {
output := fmt.Sprint(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(2),
Level: "WARN",
level: LWarn,
}
createLog(e)
}
// Error prints out logs on error level
func Error(args ...interface{}) {
output := fmt.Sprint(args...)
e := Entry{
Timestamp: time.Now(),
Output: output,
File: fileInfo(2),
Level: "ERROR",
level: LError,
}
createLog(e)
}
// Panic prints out logs on panic level
func Panic(args ...interface{}) {
output := fmt.Sprint(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
func Fatal(args ...interface{}) {
output := fmt.Sprint(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
func fileInfo(skip int) string {
_, file, line, ok := runtime.Caller(skip)
if !ok {
file = "<???>"
line = 1
} else {
slash := strings.LastIndex(file, "/")
if slash >= 0 {
file = file[slash+1:]
}
}
return fmt.Sprintf("%s:%d", file, line)
}

View File

@ -1,4 +1,4 @@
package log
package logger
import (
"strconv"
@ -55,7 +55,8 @@ func BenchmarkDebugSerial(b *testing.B) {
// Trace ensure logs come out in the right order
func TestOrder(t *testing.T) {
testString := "Testing trace: "
c := CreateClient()
var c *Client
c = CreateClient()
c.SetLogLevel(LTrace)
for i := 0; i < 5000; i++ {
@ -85,15 +86,6 @@ func TestInfo(t *testing.T) {
// }
}
// Print prints out logs on info level
func TestPrint(t *testing.T) {
// if logLevel >= LInfo {
// entry := logger.WithFields(logrus.Fields{})
// entry.Data["file"] = fileInfo(2)
// entry.Info(args...)
// }
}
// Warn prints out logs on warn level
func TestWarn(t *testing.T) {
// if logLevel >= LWarn {
@ -129,7 +121,6 @@ func TestPanic(t *testing.T) {
// entry.Panic(args...)
// }
}
func TestFlush(t *testing.T) {
defer Flush()
}

View File

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

206
main.go
View File

@ -2,16 +2,21 @@ package main
import (
"flag"
"html/template"
"log"
"net/http"
"time"
"github.com/taigrr/log-socket/browser"
logger "github.com/taigrr/log-socket/log"
"github.com/taigrr/log-socket/logger"
"github.com/taigrr/log-socket/ws"
)
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() {
for {
logger.Info("This is an info log!")
@ -19,15 +24,204 @@ func generateLogs() {
logger.Debug("This is a debug log!")
logger.Warn("This is a warn log!")
logger.Error("This is an error log!")
time.Sleep(2 * time.Second)
time.Sleep(10 * time.Second)
}
}
func main() {
defer logger.Flush()
flag.Parse()
http.HandleFunc("/ws", ws.LogSocketHandler)
http.HandleFunc("/", browser.LogSocketViewHandler)
http.HandleFunc("/logs", ws.LogSocketHandler)
http.HandleFunc("/", home)
go generateLogs()
logger.Fatal(http.ListenAndServe(*addr, nil))
log.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"
"github.com/gorilla/websocket"
logger "github.com/taigrr/log-socket/log"
"github.com/taigrr/log-socket/logger"
)
// var addr = flag.String("addr", "localhost:8080", "http service address")
@ -25,7 +25,7 @@ func LogSocketHandler(w http.ResponseWriter, r *http.Request) {
logger.Info("Websocket client attached.")
for {
logEvent := lc.Get()
logJSON, _ := json.Marshal(logEvent)
logJSON, err := json.Marshal(logEvent)
err = c.WriteMessage(websocket.TextMessage, logJSON)
if err != nil {
logger.Error("write:", err)