-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
ivans3
authored and
ivans3
committed
Jul 8, 2018
1 parent
03508a1
commit fc9e8d1
Showing
7 changed files
with
362 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
FROM alpine:latest | ||
|
||
COPY minikube-log-viewer / | ||
COPY xtail / | ||
COPY index.html / | ||
|
||
ENTRYPOINT /minikube-log-viewer | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
CGO_ENABLED=0 go build -a -installsuffix cgo -o minikube-log-viewer |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
apiVersion: v1 | ||
kind: Service | ||
metadata: | ||
name: minikube-log-viewer | ||
spec: | ||
ports: | ||
- port: 3000 | ||
name: http | ||
nodePort: 32000 | ||
selector: | ||
app: minikube-log-viewer | ||
type: NodePort | ||
|
||
--- | ||
apiVersion: extensions/v1beta1 | ||
kind: Deployment | ||
metadata: | ||
name: minikube-log-viewer | ||
namespace: default | ||
spec: | ||
strategy: | ||
type: Recreate | ||
template: | ||
metadata: | ||
labels: | ||
app: minikube-log-viewer | ||
spec: | ||
volumes: | ||
- name: logs | ||
hostPath: | ||
path: /var/log/containers | ||
- name: logs-pods | ||
hostPath: | ||
path: /var/log/pods | ||
#for minikube v0.22.2: | ||
- name: logs-containers-mnt-sda1 | ||
hostPath: | ||
path: /mnt/sda1/var/lib/docker/containers/ | ||
#for minikube v0.22.3+: | ||
- name: logs-containers | ||
hostPath: | ||
path: /var/lib/docker/containers/ | ||
hostNetwork: true | ||
containers: | ||
- name: logviewer | ||
image: minikube-log-viewer:latest | ||
imagePullPolicy: Always | ||
volumeMounts: | ||
- name: logs | ||
mountPath: /var/log/containers/ | ||
- name: logs-pods | ||
mountPath: /var/log/pods | ||
- name: logs-containers-mnt-sda1 | ||
mountPath: /mnt/sda1/var/lib/docker/containers/ | ||
- name: logs-containers | ||
mountPath: /var/lib/docker/containers/ | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
<HTML> | ||
<HEAD> | ||
</HEAD> | ||
<BODY BGCOLOR=BLACK> | ||
<STYLE> | ||
div.row { | ||
} | ||
span.timestamp { | ||
font-family: "Courier New"; | ||
color: antiquewhite; | ||
} | ||
span.msg { | ||
font-family: "Courier New"; | ||
font-size: 12px; | ||
color: lightgray; | ||
display: table-cell; | ||
width: 100%; | ||
word-break: break-all; | ||
} | ||
span.path { | ||
font-family: "Courier New"; | ||
font-size: 12px; | ||
color: antiquewhite; | ||
display: table-cell; | ||
max-width: 200px; | ||
overflow: hidden; | ||
white-space: nowrap; | ||
text-overflow: ellipsis; | ||
padding-right: 10px; | ||
} | ||
</STYLE> | ||
<SCRIPT> | ||
//config | ||
var numRows = 500 | ||
//Client-side filtering: | ||
//var fileNameMustContain = "_yournamespace_" //show only logs from containers from "yournamespace" namespace | ||
|
||
// | ||
var rows = [] | ||
|
||
function createRow(path, msg) { | ||
var div = document.createElement("div"); | ||
div.className = "row" | ||
var span1 = document.createElement("span"); | ||
span1.className = "path" | ||
span1.innerText = path | ||
div.appendChild(span1) | ||
var span2 = document.createElement("span"); | ||
span2.className = "msg" | ||
span2.innerText = msg | ||
div.appendChild(span2) | ||
document.body.insertBefore(div, document.getElementById("bottom")); | ||
return div | ||
} | ||
|
||
if (!!window.EventSource) { | ||
var source = new EventSource('/stream'); | ||
} else { | ||
// Result to xhr polling :( | ||
} | ||
|
||
source.addEventListener('message', function(e) { | ||
//console.log(e.data); | ||
var obj = JSON.parse(e.data) | ||
|
||
//if (obj.fileName.indexOf(fileNameMustContain) !== -1) { //Client-side filtering | ||
var div = createRow(obj.fileName, obj.logObject.log) | ||
window.scrollTo(0,document.body.scrollHeight); | ||
rows.push(div) | ||
if (rows.length > numRows) { | ||
var divToDelete = rows.shift() | ||
|
||
document.body.removeChild(divToDelete) | ||
} | ||
//} End Client-side filtering | ||
}, false); | ||
</SCRIPT> | ||
<DIV ID=bottom></DIV> | ||
</BODY> | ||
</HTML> | ||
|
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
// | ||
// https://gist.github.com/schmohlio/d7bdb255ba61d3f5e51a512a7c0d6a85 | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"net/http" | ||
"time" | ||
"bufio" | ||
"os/exec" | ||
"regexp" | ||
"io/ioutil" | ||
"strings" | ||
) | ||
|
||
// the amount of time to wait when pushing a message to | ||
// a slow client or a client that closed after `range clients` started. | ||
const patience time.Duration = time.Second*1 | ||
|
||
// Example SSE server in Golang. | ||
// $ go run sse.go | ||
|
||
//The BackLog | ||
var backLogLength = 500 | ||
var backLog [][]byte = make([][]byte, 0, 2*backLogLength) | ||
//Server-side filtering: | ||
//var backLogFilenameMustContain = "_yournamespace_" //dont put system logs in the backlog | ||
|
||
type Broker struct { | ||
|
||
// Events are pushed to this channel by the main events-gathering routine | ||
Notifier chan []byte | ||
|
||
// New client connections | ||
newClients chan chan []byte | ||
|
||
// Closed client connections | ||
closingClients chan chan []byte | ||
|
||
// Client connections registry | ||
clients map[chan []byte]bool | ||
|
||
} | ||
|
||
func NewServer() (broker *Broker) { | ||
// Instantiate a broker | ||
broker = &Broker{ | ||
Notifier: make(chan []byte, 1), | ||
newClients: make(chan chan []byte), | ||
closingClients: make(chan chan []byte), | ||
clients: make(map[chan []byte]bool), | ||
} | ||
|
||
// Set it running - listening and broadcasting events | ||
go broker.listen() | ||
|
||
return | ||
} | ||
|
||
func (broker *Broker) ServeHTTP(rw http.ResponseWriter, req *http.Request) { | ||
|
||
if (req.URL.Path == "/") { | ||
rw.Header().Set("Content-Type", "text/html") | ||
dat, _ := ioutil.ReadFile("/index.html") | ||
//TODO: error handling | ||
rw.Write(dat) | ||
return | ||
} | ||
if (req.URL.Path == "/debug") { | ||
rw.Header().Set("Content-Type", "text/plain") | ||
fmt.Fprintf(rw, "length = %d\n", len(backLog)) | ||
|
||
return | ||
} | ||
|
||
// Make sure that the writer supports flushing. | ||
// | ||
flusher, ok := rw.(http.Flusher) | ||
|
||
if !ok { | ||
http.Error(rw, "Streaming unsupported!", http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
rw.Header().Set("Content-Type", "text/event-stream") | ||
rw.Header().Set("Cache-Control", "no-cache") | ||
rw.Header().Set("Connection", "keep-alive") | ||
rw.Header().Set("Access-Control-Allow-Origin", "*") | ||
|
||
// Each connection registers its own message channel with the Broker's connections registry | ||
messageChan := make(chan []byte) | ||
|
||
// Signal the broker that we have a new connection | ||
broker.newClients <- messageChan | ||
|
||
// Remove this client from the map of connected clients | ||
// when this handler exits. | ||
defer func() { | ||
broker.closingClients <- messageChan | ||
}() | ||
|
||
// Listen to connection close and un-register messageChan | ||
notify := rw.(http.CloseNotifier).CloseNotify() | ||
|
||
//dump the backLog | ||
for _, element := range backLog { | ||
fmt.Fprintf(rw, "data: %s\n\n", element) | ||
} | ||
flusher.Flush() | ||
|
||
for { | ||
select { | ||
case <-notify: | ||
return | ||
default: | ||
|
||
// Write to the ResponseWriter | ||
// Server Sent Events compatible | ||
fmt.Fprintf(rw, "data: %s\n\n", <-messageChan) | ||
|
||
// Flush the data immediatly instead of buffering it for later. | ||
flusher.Flush() | ||
} | ||
} | ||
|
||
} | ||
|
||
func (broker *Broker) listen() { | ||
for { | ||
select { | ||
case s := <-broker.newClients: | ||
|
||
// A new client has connected. | ||
// Register their message channel | ||
broker.clients[s] = true | ||
log.Printf("Client added. %d registered clients", len(broker.clients)) | ||
case s := <-broker.closingClients: | ||
|
||
// A client has dettached and we want to | ||
// stop sending them messages. | ||
delete(broker.clients, s) | ||
log.Printf("Removed client. %d registered clients", len(broker.clients)) | ||
case event := <-broker.Notifier: | ||
|
||
// We got a new event from the outside! | ||
// Send event to all connected clients | ||
for clientMessageChan, _ := range broker.clients { | ||
select { | ||
case clientMessageChan <- event: | ||
case <-time.After(patience): | ||
log.Print("Skipping client.") | ||
} | ||
} | ||
} | ||
} | ||
|
||
} | ||
|
||
func main() { | ||
|
||
broker := NewServer() | ||
|
||
cmd := exec.Command("/xtail","/var/log/containers") | ||
|
||
stdout, err := cmd.StdoutPipe() | ||
checkError(err) | ||
err = cmd.Start() | ||
checkError(err) | ||
defer cmd.Wait() // Doesn't block | ||
|
||
scanner := bufio.NewScanner(stdout) | ||
|
||
currentFile := "" | ||
re1 := regexp.MustCompile("^\\*\\*\\* /var/log/containers/(?P<Path>.*) \\*\\*\\*$") | ||
|
||
go func() { | ||
for scanner.Scan() { | ||
eventString := scanner.Text() | ||
m1 := re1.FindStringSubmatch(eventString) | ||
if (m1 != nil) { | ||
currentFile = m1[1] | ||
//log.Println("File: "+currentFile) | ||
} else if (eventString != "") && (! strings.HasPrefix(eventString, "***")) { | ||
jsonBytes := []byte("{\"fileName\":\""+currentFile+"\",\"logObject\":"+eventString+"}") | ||
|
||
//log.Println("Receiving event") | ||
broker.Notifier <- jsonBytes | ||
//put it on the backlog | ||
//if (strings.Contains(currentFile, backLogFilenameMustContain)) { | ||
backLog = append(backLog, jsonBytes) | ||
if (len(backLog) >= backLogLength) { | ||
//shift | ||
backLog = backLog[1:] | ||
} | ||
//} | ||
} else { | ||
//fmt.Println("Ignoring line") | ||
} | ||
} | ||
|
||
|
||
}() | ||
|
||
log.Fatal("HTTP server error: ", http.ListenAndServe(":3000", broker)) | ||
|
||
} | ||
|
||
func checkError(err error) { | ||
if err != nil { | ||
log.Fatalf("Error: %s", err) | ||
} | ||
} | ||
|