blob: 4903b4d0a1a295d99244d40e95db9a44e310b8d8 [file] [log] [blame]
;; Licensed to the Apache Software Foundation (ASF) under one
;; or more contributor license agreements. See the NOTICE file
;; distributed with this work for additional information
;; regarding copyright ownership. The ASF licenses this file
;; to you under the Apache License, Version 2.0 (the
;; "License"); you may not use this file except in compliance
;; with the License. You may obtain a copy of the License at
;;
;; http://www.apache.org/licenses/LICENSE-2.0
;;
;; Unless required by applicable law or agreed to in writing, software
;; distributed under the License is distributed on an "AS IS" BASIS,
;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;; See the License for the specific language governing permissions and
;; limitations under the License.
(ns backtype.storm.daemon.logviewer
(:use compojure.core)
(:use [hiccup core page-helpers])
(:use [backtype.storm config util log])
(:use [ring.adapter.jetty :only [run-jetty]])
(:import [org.slf4j LoggerFactory])
(:import [ch.qos.logback.classic Logger])
(:import [ch.qos.logback.core FileAppender])
(:import [java.io File])
(:require [compojure.route :as route]
[compojure.handler :as handler]
[clojure.string :as string])
(:gen-class))
(defn tail-file [path tail root-dir]
(let [flen (.length (clojure.java.io/file path))
skip (- flen tail)
log-dir (.getCanonicalFile (File. root-dir))
log-file (File. path)]
(if (= log-dir (.getParentFile log-file))
(with-open [input (clojure.java.io/input-stream path)
output (java.io.ByteArrayOutputStream.)]
(if (> skip 0) (.skip input skip))
(let [buffer (make-array Byte/TYPE 1024)]
(loop []
(let [size (.read input buffer)]
(when (and (pos? size) (< (.size output) tail))
(do (.write output buffer 0 size)
(recur))))))
(.toString output)) "File not found")
))
(defn log-root-dir
"Given an appender name, as configured, get the parent directory of the appender's log file.
Note that if anything goes wrong, this will throw an Error and exit."
[appender-name]
(let [appender (.getAppender (LoggerFactory/getLogger Logger/ROOT_LOGGER_NAME) appender-name)]
(if (and appender-name appender (instance? FileAppender appender))
(.getParent (File. (.getFile appender)))
(throw
(RuntimeException. "Log viewer could not find configured appender, or the appender is not a FileAppender. Please check that the appender name configured in storm and logback agree.")))))
(defn log-page [file tail grep root-dir]
(let [path (.getCanonicalPath (File. root-dir file))
tail (if tail
(min 10485760 (Integer/parseInt tail))
10240)
tail-string (tail-file path tail root-dir)]
(if grep
(clojure.string/join "\n<br>"
(filter #(.contains % grep) (.split tail-string "\n")))
(.replaceAll tail-string "\n" "\n<br>"))))
(defn log-template [body]
(html4
[:head
[:title "Storm log viewer"]
(include-css "/css/bootstrap-1.4.0.css")
(include-css "/css/style.css")
(include-js "/js/jquery-1.6.2.min.js")
(include-js "/js/jquery.tablesorter.min.js")
(include-js "/js/jquery.cookies.2.2.0.min.js")
(include-js "/js/script.js")
]
[:body
(seq body)
]))
(defroutes log-routes
(GET "/log" [:as req & m]
(log-template (log-page (:file m) (:tail m) (:grep m) (:log-root req))))
(route/resources "/")
(route/not-found "Page not found"))
(def logapp
(handler/site log-routes)
)
(defn conf-middleware
"For passing the storm configuration with each request."
[app log-root]
(fn [req]
(app (assoc req :log-root log-root))))
(defn start-logviewer [port log-root]
(run-jetty (conf-middleware logapp log-root) {:port port}))
(defn -main []
(let [conf (read-storm-config)
log-root (log-root-dir (conf LOGVIEWER-APPENDER-NAME))]
(start-logviewer (int (conf LOGVIEWER-PORT)) log-root)))