├── .gitignore ├── unit-tests ├── .gitignore ├── runTests ├── MeteorStubs.coffee ├── EventLogger.coffee ├── Cakefile └── test.coffee ├── tests ├── tests.css ├── runTests ├── tests.html ├── smart.json ├── smart.lock └── tests │ ├── test.coffee │ └── test.js ├── .DS_Store ├── lib ├── .DS_Store ├── Observatory.coffee ├── Settings.coffee ├── client │ ├── Observatory.coffee │ ├── Settings.coffee │ └── templates.coffee ├── MeteorLogger.coffee └── server │ ├── MeteorInternals.coffee │ ├── DDPEmitter.coffee │ ├── HttpEmitter.coffee │ ├── MonitoringEmitter.coffee │ ├── Observatory.coffee │ ├── DDPConnectionEmitter.coffee │ ├── Settings.coffee │ └── ObservatoryServer.coffee ├── globals.js ├── .gitmodules ├── package.js ├── monitoringHooks.coffee ├── versions.json ├── README.md └── LICENSE.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .build* 2 | -------------------------------------------------------------------------------- /unit-tests/.gitignore: -------------------------------------------------------------------------------- 1 | *.js -------------------------------------------------------------------------------- /tests/tests.css: -------------------------------------------------------------------------------- 1 | /* CSS declarations go here */ 2 | -------------------------------------------------------------------------------- /tests/runTests: -------------------------------------------------------------------------------- 1 | coffee -w -c tests/*.coffee & 2 | mrt 3 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superstringsoftware/observatory/HEAD/.DS_Store -------------------------------------------------------------------------------- /lib/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superstringsoftware/observatory/HEAD/lib/.DS_Store -------------------------------------------------------------------------------- /unit-tests/runTests: -------------------------------------------------------------------------------- 1 | mocha -R spec --watch --compilers coffee:coffee-script ttt.coffee 2 | 3 | -------------------------------------------------------------------------------- /globals.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by aantich on 20/09/14. 3 | */ 4 | Observatory = this.Observatory; 5 | TLog = this.TLog; 6 | -------------------------------------------------------------------------------- /tests/tests.html: -------------------------------------------------------------------------------- 1 | 2 | tests 3 | 4 | 5 | 6 |
7 | {{> mochaTestReport}} 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/lib/galileo"] 2 | path = lib/lib/galileo 3 | url = https://github.com/superstringsoftware/observatory-galileo.git 4 | [submodule "lib/lib/observatory-galileo"] 5 | path = lib/lib/observatory-galileo 6 | url = https://github.com/superstringsoftware/observatory-galileo.git 7 | -------------------------------------------------------------------------------- /tests/smart.json: -------------------------------------------------------------------------------- 1 | { 2 | "meteor": { 3 | "git": "https://github.com/meteor/meteor.git", 4 | "branch": "master" 5 | }, 6 | "packages": { 7 | "observatory-apollo": { 8 | "path": "../" 9 | }, 10 | "observatory-testing": { 11 | "path": "../../observatory-testing" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/Observatory.coffee: -------------------------------------------------------------------------------- 1 | # COMMON 2 | 3 | Observatory = @Observatory ? {} 4 | 5 | # This is KEY for handshakes and stuff 6 | Observatory.version = 7 | major: 0 8 | minor: 4 9 | patch: 8 10 | 11 | # changing server definition function to meteor specific 12 | Observatory.isServer = -> Meteor.isServer 13 | # defining getter for the meteor logger 14 | Observatory.getMeteorLogger = -> Observatory._meteorLogger 15 | 16 | Observatory.initialize() 17 | 18 | 19 | (exports ? this).Observatory = Observatory -------------------------------------------------------------------------------- /lib/Settings.coffee: -------------------------------------------------------------------------------- 1 | Observatory = @Observatory ? {} 2 | 3 | # Settings 4 | class Observatory.SettingsCommon 5 | @defaultClientSettings: 6 | logLevel: "DEBUG", printToConsole: true, logUser: true, logAnonymous: true 7 | 8 | # settings collection 9 | @col: new Mongo.Collection('_observatory_settings') 10 | 11 | processSettingsUpdate: (s) -> Observatory.setSettings(s) 12 | 13 | currentSettings: -> throw new Error "SettingsCommon::currentSettings - needs overloading and should not be called directly" 14 | 15 | 16 | (exports ? this).Observatory = Observatory -------------------------------------------------------------------------------- /tests/smart.lock: -------------------------------------------------------------------------------- 1 | { 2 | "meteor": { 3 | "git": "https://github.com/meteor/meteor.git", 4 | "branch": "master", 5 | "commit": "f07715dc70de16a7aab84e56ab0c6cbd9c1f9dc6" 6 | }, 7 | "dependencies": { 8 | "basePackages": { 9 | "observatory-apollo": { 10 | "path": ".." 11 | }, 12 | "observatory-testing": { 13 | "path": "../../observatory-testing" 14 | } 15 | }, 16 | "packages": { 17 | "observatory-apollo": { 18 | "path": ".." 19 | }, 20 | "observatory-testing": { 21 | "path": "../../observatory-testing" 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /unit-tests/MeteorStubs.coffee: -------------------------------------------------------------------------------- 1 | # stub for testing, if we are in the test env Meteor won't be defined 2 | Meteor = 3 | isServer: true 4 | isClient: false 5 | Collection: (name)-> 6 | @name = name 7 | allow: (fn)-> 8 | #console.log "collection allow called" 9 | insert: -> 10 | #console.log "collection insert called" 11 | startup: (fn)-> 12 | fn.call this, arguments 13 | setInterval: (fn)-> 14 | #console.log "setInterval called" 15 | publish: (name, fn)-> 16 | #console.log "Publishing #{name}" 17 | default_server: 18 | stream_server: 19 | register: (func)-> 20 | 21 | EJSON = JSON 22 | 23 | WebApp = 24 | connectHandlers: 25 | use:(name)-> 26 | #console.log "WebApp.connectHandlers.use #{name} called" 27 | 28 | Observatory = 29 | logger: -> 30 | #console.log "logger called" 31 | 32 | #module.exports = this 33 | #module.exports.Meteor = Meteor 34 | #module.exports.EJSON = EJSON 35 | #(exports ? this).Meteor = @Meteor 36 | 37 | -------------------------------------------------------------------------------- /lib/client/Observatory.coffee: -------------------------------------------------------------------------------- 1 | # CLIENT 2 | 3 | Observatory = @Observatory ? {} 4 | 5 | ### 6 | # TODO: REDEFINE to be server slave 7 | Observatory.initialize = _.wrap Observatory.initialize, (f, s)-> 8 | f.call Observatory, s 9 | 10 | # TODO: REDEFINE to be server slave 11 | Observatory.setSettings = _.wrap Observatory.setSettings, (f, s)-> 12 | # calling base function 13 | f.call Observatory, s 14 | ### 15 | 16 | # adding meteor-specific initialization 17 | # TODO: NOTE!!! Logger on the client is created only after we've received settings from the server, which may or may not be good - think through! 18 | Observatory.registerInitFunction (s)-> 19 | #console.log Meteor 20 | @settingsController = new Observatory.Settings 21 | Meteor.startup => 22 | #console.log 'subscribed' 23 | #@settings = Observatory.settingsController.currentSettings() 24 | #console.log @settings 25 | @_meteorLogger = new Observatory.MeteorLogger 'Meteor Logger' 26 | @subscribeLogger @_meteorLogger 27 | 28 | 29 | (exports ? this).Observatory = Observatory -------------------------------------------------------------------------------- /lib/client/Settings.coffee: -------------------------------------------------------------------------------- 1 | Observatory = @Observatory ? {} 2 | 3 | # Settings 4 | class Observatory.Settings extends Observatory.SettingsCommon 5 | 6 | constructor: -> 7 | @col = Observatory.SettingsCommon.col 8 | @processSettingsUpdate @currentSettings() 9 | # observing the settings changes and rerunning the setup 10 | @col.find().observe { 11 | changed: (newDoc, oldDoc)=> 12 | @processSettingsUpdate(newDoc.settings) 13 | 14 | } 15 | # autorunning to make sure of re-subscription if the user id changes 16 | Meteor.startup => 17 | Tracker.autorun => 18 | uid = if Accounts? then Meteor.userId() else null 19 | @sub = Meteor.subscribe '_observatory_settings', {uid: uid, connectionId: Meteor.connection._lastSessionId}, { 20 | onError: (err)-> 21 | console.log err 22 | onReady: => 23 | newS = @currentSettings() 24 | @processSettingsUpdate(newS) 25 | } 26 | 27 | ready: -> @sub.ready() 28 | 29 | processSettingsUpdate: (s)-> 30 | super s 31 | #console.log s 32 | 33 | currentSettings: -> @col.findOne()?.settings ? Observatory.SettingsCommon.defaultClientSettings 34 | 35 | 36 | (exports ? this).Observatory = Observatory -------------------------------------------------------------------------------- /unit-tests/EventLogger.coffee: -------------------------------------------------------------------------------- 1 | chai = require 'chai' 2 | #console.log Meteor 3 | should = chai.should() 4 | #EventLogger = (require '../lib/EventLogger.coffee').Observatory.EventLogger 5 | #DDPLogger = (require '../lib/server/DDPLogger.coffee').Observatory.DDPLogger 6 | 7 | describe 'EventLogger - base class for system loggers (http, ddp...)', -> 8 | lg = new Observatory.EventLogger "newLogger" 9 | it 'should be created with empty buffer and name set correctly',-> 10 | lg.logsBuffer.should.be.empty 11 | lg.name.should.equal "newLogger" 12 | #lg.func.should.equal null 13 | 14 | it 'should add buffer objects correctly, process them and reset buffer to empty',-> 15 | obj1 = a: 'a', b: 1 16 | obj2 = a: 'a', b: 2 17 | lg.addObjectToBuffer obj1 18 | lg.addObjectToBuffer obj2 19 | lg.logsBuffer.length.should.equal 2 20 | lg.logsBuffer[0].should.equal obj1 21 | lg.logsBuffer[1].should.equal obj2 22 | lg.processBuffer (obj)-> 23 | obj.a.should.equal 'a' 24 | lg.logsBuffer.should.be.empty 25 | 26 | 27 | describe 'DDPLogger - logs ddp messages on the server', -> 28 | 29 | it 'should be created with empty buffer and name set to DDP',-> 30 | lg = new Observatory.DDPLogger 31 | lg.logsBuffer.should.be.empty 32 | lg.name.should.equal "DDP" 33 | lg.func.should.exist -------------------------------------------------------------------------------- /unit-tests/Cakefile: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | {print} = require 'util' 3 | {spawn, exec} = require "child_process" 4 | 5 | listOfFiles = [ 6 | "MeteorStubs.coffee ../lib/TLog.coffee test.coffee" 7 | , 8 | "../lib/EventLogger.coffee ../lib/server/DDPLogger.coffee EventLogger.coffee" 9 | ] 10 | 11 | 12 | files = ["MeteorStubs.coffee", "../TLog.coffee", "test.coffee"] 13 | 14 | task "compile", "Compile unit tests", -> 15 | for lf,i in listOfFiles 16 | exec " coffee -j test#{i}.js -c " + lf, (err, stdout, stderr) -> 17 | throw err if err 18 | console.log stdout + stderr 19 | 20 | task "test", "Run unit tests", -> 21 | exec " mocha -R spec --colors *.js", (err, stdout, stderr)-> 22 | #throw err if err 23 | console.log stdout + stderr 24 | 25 | 26 | # TODO: need to build a proper recursion 27 | buildBatch = (inFiles, outFile, cb)-> exec " coffee -j #{outFile} -c " + inFiles, cb 28 | buildAll = (cb)-> 29 | for lf,i in listOfFiles 30 | buildBatch lf, "test#{i}.js", cb 31 | 32 | 33 | task "watch", "Run unit tests watching for changes", -> 34 | coffee = spawn 'coffee', ['-w', '-j', 'tests.js', '-c'].concat files 35 | coffee.stderr.on 'data', (data) -> process.stderr.write data.toString() 36 | coffee.stdout.on 'data', (data) -> print data.toString() 37 | coffee.on 'exit', (code) -> print 'Finished coffee' + code 38 | console.log 'Spawned coffee' 39 | mocha = spawn 'mocha', ['-R', 'spec', '--watch', '--colors', '*.js'] 40 | console.log 'Spawned mocha' 41 | mocha.stdout.pipe process.stdout, end: false 42 | mocha.stderr.pipe process.stderr, end: false 43 | mocha.on 'exit', (code) -> callback?(code,code) -------------------------------------------------------------------------------- /unit-tests/test.coffee: -------------------------------------------------------------------------------- 1 | chai = require 'chai' 2 | #console.log Meteor 3 | should = chai.should() 4 | 5 | describe 'TLog class', -> 6 | #TLog = TLogModule.TLog 7 | #console.dir TLog 8 | tl = TLog.getLogger() 9 | it 'should be visible, have the global logs collection and have correct default options',-> 10 | #console.log TLog 11 | TLog.should.exist 12 | TLog._global_logs.should.exist 13 | TLog._connectLogsBuffer.should.be.empty 14 | TLog._ddpLogsBuffer.should.be.empty 15 | TLog._log_http.should.be.true 16 | TLog._log_DDP.should.be.true 17 | 18 | it 'should return the default logger with correct defaults', -> 19 | tl.should.exist 20 | tl.should.be.an.instanceof TLog 21 | tl._currentLogLevel.should.equal TLog.LOGLEVEL_DEBUG 22 | tl._log_user.should.be.true 23 | tl._printToConsole.should.be.false 24 | it 'should add http logs to buffer and clean them after processing',-> 25 | httpLog = 26 | timestamp: new Date 27 | TLog.addToLogsBuffer(httpLog) 28 | TLog._connectLogsBuffer.length.should.equal 1 29 | TLog._connectLogsBuffer[0].should.equal httpLog 30 | TLog.checkConnectLogsBuffer() 31 | TLog._connectLogsBuffer.should.be.empty 32 | 33 | 34 | describe 'Logging methods:', -> 35 | it "should call methods with all log levels", -> 36 | for m,i in ['fatal','error','warn','info','verbose','debug','insaneVerbose'] 37 | tl.should.respondTo m 38 | tl[m] "Logging #{TLog.LOGLEVEL_NAMES[i]} message", "TESTS" 39 | for m,i in ['trace','dir'] 40 | tl.should.respondTo m 41 | tl[m] new Error("Test Error"), "Message for #{m}" 42 | 43 | -------------------------------------------------------------------------------- /lib/MeteorLogger.coffee: -------------------------------------------------------------------------------- 1 | Observatory = @Observatory ? {} 2 | 3 | class Observatory.MeteorLogger extends Observatory.Logger 4 | constructor: (@name, @colName = '_observatory_logs', connection = null) -> 5 | super @name 6 | @_logsCollection = new Mongo.Collection @colName 7 | # can't update logs; setting up pointers to insert and remove allow functions 8 | if Meteor.isServer 9 | @_logsCollection.allow 10 | update: (uid)-> 11 | false 12 | insert: (uid)=> 13 | @allowInsert uid 14 | remove: (uid)=> 15 | @allowRemove uid 16 | 17 | 18 | 19 | 20 | 21 | # redefine these functions anytime on server side to be able to control what gets logged - 22 | # useful when in production and want to control what comes from the clients: 23 | # Observatory._meteorLogger.allowInsert = (uid) -> ... 24 | allowInsert: (uid)-> 25 | false 26 | allowRemove: (uid)-> 27 | false 28 | 29 | # overriding the main logging method 30 | log: (message)=> 31 | if Meteor.isClient 32 | #console.log "logging..." 33 | #console.dir message 34 | if not Observatory.settingsController.currentSettings().logAnonymous 35 | if not Observatory.settingsController.currentSettings().logUser 36 | return 37 | else 38 | return unless Meteor.userId()? 39 | 40 | #console.log "Logging in Meteor Server: #{Meteor.isServer}" 41 | msg = message # do we really need the clone thing?? 42 | msg.userId = msg.userId ? @_checkUserId() 43 | msg.connectionId = Meteor.connection._lastSessionId if not Meteor.isServer 44 | #console.log msg 45 | @_logsCollection.insert msg 46 | 47 | logsCount: -> @_logsCollection.find().count() 48 | 49 | # helper method to get userId 50 | # TODO: think how to get to it if we are in publish() 51 | # TODO: Needs testing! 52 | # TODO: Maybe just record uid and display actual user info when, uhm, displaying logs? 53 | _checkUserId: => 54 | #console.log @ 55 | uid = null 56 | try 57 | uid = this.userId ? Meteor.userId() 58 | #console.log uid 59 | #return uid 60 | catch err 61 | #console.log err 62 | uid 63 | 64 | 65 | (exports ? this).Observatory = Observatory -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | try { 2 | Package.describe({ 3 | summary: "Versatile and powerful logging and application management (http://observatoryjs.com)", 4 | "version": "0.4.6", 5 | "git": "https://github.com/superstringsoftware/observatory.git" 6 | 7 | }); 8 | 9 | Package.on_use(function (api) { 10 | api.versionsFrom("METEOR@0.9.0"); 11 | //console.log("loading observatory: apollo -- now working with Meteor 0.9"); 12 | api.use(['coffeescript', 'underscore','standard-app-packages','ejson'], ['client','server']); 13 | api.use('accounts-password', 'server') 14 | //api.use(['webapp'], ['server']); 15 | 16 | // galileo files 17 | api.add_files(['lib/lib/observatory-galileo/src/Observatory.coffee','lib/lib/observatory-galileo/src/Toolbox.coffee'],['client','server']); 18 | 19 | api.add_files('lib/server/MeteorInternals.coffee','server'); 20 | api.add_files('lib/server/ObservatoryServer.coffee','server'); 21 | api.add_files('lib/server/DDPEmitter.coffee','server'); 22 | api.add_files('lib/server/DDPConnectionEmitter.coffee','server'); 23 | api.add_files('lib/server/HttpEmitter.coffee','server'); 24 | api.add_files('lib/server/MonitoringEmitter.coffee','server'); 25 | api.add_files('lib/MeteorLogger.coffee',['client','server']); 26 | //api.add_files('lib/server/monitoringHooks.coffee','server'); 27 | 28 | api.add_files('lib/Settings.coffee',['client','server']); 29 | api.add_files('lib/server/Settings.coffee','server'); 30 | api.add_files('lib/client/Settings.coffee','client'); 31 | 32 | 33 | api.add_files('lib/server/Observatory.coffee','server'); 34 | api.add_files('lib/client/Observatory.coffee','client'); 35 | 36 | api.add_files('lib/client/templates.coffee','client'); 37 | 38 | api.add_files(['lib/Observatory.coffee'],['client','server']); 39 | 40 | api.add_files('globals.js',['client','server']); 41 | 42 | api.export ('Observatory'); 43 | api.export ('TLog'); 44 | 45 | 46 | 47 | //console.dir(Observatory); 48 | 49 | 50 | }); 51 | } 52 | catch (err) { 53 | console.log("Error while trying to load a package: " + err.message); 54 | } 55 | -------------------------------------------------------------------------------- /lib/server/MeteorInternals.coffee: -------------------------------------------------------------------------------- 1 | # Abstraction of different hidden Meteor methods that help us in monitoring / management 2 | 3 | Observatory = @Observatory ? {} 4 | 5 | class Observatory.MeteorInternals 6 | 7 | # returns all currently open sessions 8 | getCurrentSessions: -> Meteor.server.sessions 9 | getSessionCount: -> _.keys(@getCurrentSessions()).length 10 | 11 | # finds session by id (is session id the same as connection?) 12 | findSession: (id) -> _.find @getCurrentSessions(), (v,k)-> k is id 13 | 14 | getCurrentServer: -> 15 | #console.log "======================================== called getCurrentServer ======================================" 16 | #console.dir Meteor.server 17 | srv = Meteor.server 18 | publishHandlers = ({name: k, func: v.toString().substring(0,v.toString().indexOf('{') - 1 ), body: v.toString().substring(v.toString().indexOf('{')) } for k,v of srv?.publish_handlers) 19 | methodHandlers = ({name: k, func: v.toString().substring(0,v.toString().indexOf('{') - 1 ), body: v.toString().substring(v.toString().indexOf('{')) } for k,v of srv?.method_handlers) 20 | {publishHandlers: publishHandlers, methodHandlers: methodHandlers} 21 | 22 | 23 | convertSessionToView: (ss)-> 24 | #console.log "==============================================================================================" 25 | #console.dir ss.socket 26 | session = 27 | id: ss.id 28 | connectionId: ss.connectionHandle.id 29 | ip: ss.connectionHandle.clientAddress 30 | headers: ss.connectionHandle.httpHeaders 31 | userId: ss.userId 32 | collectionViews: [] 33 | namedSubs: [] 34 | 35 | for k,v of ss.collectionViews 36 | cv = 37 | id: k 38 | name: v.collectionName 39 | documentCount: _.keys(v.documents).length 40 | session.collectionViews.push cv 41 | 42 | for k,v of ss._namedSubs 43 | ns = 44 | name: v._name 45 | params: v._params 46 | subscriptionHandle: v._subscriptionHandle 47 | deactivated: v._deactivated 48 | documentCount: _.keys(v._documents).length 49 | ready: v._ready 50 | session.namedSubs.push ns 51 | 52 | session 53 | 54 | 55 | 56 | 57 | 58 | (exports ? this).Observatory = Observatory -------------------------------------------------------------------------------- /lib/server/DDPEmitter.coffee: -------------------------------------------------------------------------------- 1 | 2 | Observatory = @Observatory ? {} 3 | 4 | class Observatory.DDPEmitter extends @Observatory.MessageEmitter 5 | @messageStub: -> 6 | options = 7 | isServer: true 8 | severity: Observatory.LOGLEVEL.DEBUG 9 | module: "DDP" 10 | timestamp: new Date 11 | options 12 | 13 | @_instance: undefined 14 | 15 | # getter for the instance 16 | @de: => 17 | @_instance?= new Observatory.DDPEmitter "DDP Emitter" 18 | @_instance 19 | 20 | constructor: (@name, @formatter)-> 21 | #console.log "DDPEmitter::constructor #{name}" 22 | super @name, @formatter 23 | @turnOff() 24 | if Observatory.DDPEmitter._instance? then throw new Error "Attempted to create another instance of DDPEmitter and it is a really bad idea" 25 | # registering to listen to socket events with Meteor 26 | Meteor.default_server.stream_server.register (socket)-> 27 | return unless Observatory.DDPEmitter.de().isOn 28 | #console.log socket._session.connection 29 | msg = Observatory.DDPEmitter.messageStub() 30 | msg.socketId = socket.id 31 | msg.textMessage = "Connected socket #{socket.id}" 32 | # emitting message and putting to the buffer for the sake of Meteor logging. Insensitive loggers, such as Console, 33 | # should actually ignore this 34 | #console.log msg 35 | Observatory.DDPEmitter.de().emitMessage msg, true 36 | 37 | socket.on 'data', (raw_msg)-> 38 | #console.log @_session.connection._meteorSession.id 39 | return unless Observatory.DDPEmitter.de().isOn 40 | msg = Observatory.DDPEmitter.messageStub() 41 | msg.socketId = @id 42 | msg.sessionId = @_session.connection._meteorSession.id 43 | msg.textMessage = "Got message in a socket #{@id} session #{@_session.connection._meteorSession.id}" 44 | msg.object = raw_msg 45 | msg.type = "DDP" 46 | #console.log msg 47 | Observatory.DDPEmitter.de().emitMessage msg, true 48 | 49 | socket.on 'close', -> 50 | return unless Observatory.DDPEmitter.de().isOn 51 | msg = Observatory.DDPEmitter.messageStub() 52 | msg.socketId = socket.id 53 | msg.textMessage = "Closed socket #{socket.id}" 54 | #console.log msg 55 | Observatory.DDPEmitter.de().emitMessage msg, true 56 | 57 | 58 | 59 | 60 | 61 | (exports ? this).Observatory = Observatory -------------------------------------------------------------------------------- /tests/tests/test.coffee: -------------------------------------------------------------------------------- 1 | if Meteor.isServer 2 | require = if Npm? then Npm.require else __meteor_bootstrap__.require 3 | Fiber = require 'fibers' 4 | else 5 | # stub for the client 6 | Fiber = (fn)-> 7 | run:->fn() 8 | 9 | should = chai.should() 10 | 11 | describe 'TLog class', -> 12 | tl = TLog.getLogger() 13 | TLog._clear() if Meteor.isServer 14 | it 'should be visible, have the global logs collection and log http by default',-> 15 | #console.log TLog 16 | TLog.should.exist 17 | TLog._global_logs.should.exist 18 | TLog._log_http.should.be.true 19 | it 'should return the default logger with correct defaults', -> 20 | tl.should.exist 21 | tl.should.be.an.instanceof TLog 22 | tl._currentLogLevel.should.equal TLog.LOGLEVEL_DEBUG 23 | tl._log_user.should.be.true 24 | tl._printToConsole.should.be.false 25 | 26 | describe 'Logging methods:', -> 27 | it "should call methods with all log levels", -> 28 | for m,i in ['fatal','error','warn','info','verbose','debug','insaneVerbose'] 29 | tl.should.respondTo m 30 | tl[m] "Logging #{TLog.LOGLEVEL_NAMES[i]} message", "TESTS" 31 | for m,i in ['trace','dir'] 32 | tl.should.respondTo m 33 | tl[m] new Error("Test Error"), "Message for #{m}" 34 | 35 | # TODO: if something fails inside the Fiber, everything breaks, need to figure out how to resume execution flow 36 | it 'should log the message to the database correctly', (done)-> 37 | obj = 38 | message: "Logging #{TLog.LOGLEVEL_NAMES[TLog.LOGLEVEL_INFO]} message" 39 | full_message: "Logging #{TLog.LOGLEVEL_NAMES[TLog.LOGLEVEL_INFO]} FULL message" 40 | module: "TESTS" 41 | timestamp: new Date 42 | isServer: Meteor.isServer 43 | customOptions = {cust1: "cust1", cust2: 10} 44 | 45 | rec = null 46 | # need to wrap this in Fiber in Meteor... 47 | f = new Fiber -> 48 | tl._lowLevelLog TLog.LOGLEVEL_INFO, obj, customOptions, (err, id)-> 49 | if (err) then throw err 50 | rec = TLog._global_logs.findOne id 51 | #console.dir rec 52 | rec.should.exist 53 | obj.message.should.equal rec.message 54 | obj.full_message.should.equal rec.full_message 55 | obj.module.should.equal rec.module 56 | obj.timestamp.should.eql rec.timestamp 57 | obj.isServer.should.equal rec.isServer 58 | rec.customOptions.should.deep.equal customOptions 59 | done() 60 | 61 | f.run() 62 | #done() 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /lib/server/HttpEmitter.coffee: -------------------------------------------------------------------------------- 1 | Observatory = @Observatory ? {} 2 | 3 | class Observatory.HttpEmitter extends @Observatory.MessageEmitter 4 | 5 | httpLogger: (req, res, next) => 6 | if @isOff 7 | next() 8 | return 9 | 10 | req._startTime = Date.now() 11 | end = res.end 12 | 13 | res.end = (chunk, encoding) => 14 | res.end = end 15 | res.end chunk, encoding 16 | 17 | # TODO: LOG HERE!!! 18 | timeElaspsed = Date.now() - req._startTime 19 | obj = 20 | url: req.originalUrl or req.url 21 | method: req.method 22 | referrer: req.headers["referer"] or req.headers["referrer"] 23 | remoteAddress: 24 | if req.ip 25 | req.ip 26 | else 27 | if req.socket.socket 28 | req.socket.socket.remoteAddress 29 | else 30 | req.socket.remoteAddress 31 | status: res.statusCode 32 | httpVersion: req.httpVersionMajor + "." + req.httpVersionMinor 33 | userAgent: req.headers["user-agent"] 34 | #contentLength: parseInt(res.getHeader('Content-Length'), 10) 35 | responseHeader: res._header 36 | acceptLanguage: req.headers['accept-language'] 37 | forwardedFor: req.headers['x-forwarded-for'] 38 | #requestHeaders: req.headers 39 | timestamp: new Date 40 | responseTime: timeElaspsed 41 | timeElaspsed: timeElaspsed 42 | type: 'http' 43 | 44 | #console.dir obj 45 | @emitFormattedMessage obj, true 46 | next() 47 | 48 | constructor: (@name)-> 49 | @turnOff() 50 | 51 | @formatter = (l)-> 52 | #"#{l.method} #{l.url}: #{l.status} in #{l.responseTime} ms\n#{l.userAgent}\n#{l.responseHeader}\nreferrer: #{l.referrer?}" 53 | msg = "#{l.method} #{l.url}: #{l.status} from #{l.forwardedFor} in #{l.responseTime} ms" 54 | severity = Observatory.LOGLEVEL.VERBOSE 55 | if l.status >= 500 then severity = Observatory.LOGLEVEL.FATAL 56 | else 57 | if l.status >= 400 58 | severity = Observatory.LOGLEVEL.ERROR 59 | else 60 | if l.status >= 300 then severity = Observatory.LOGLEVEL.WARNING 61 | options = 62 | isServer: true 63 | textMessage: msg 64 | module: "HTTP" 65 | timestamp: l.timestamp 66 | type: 'profile' 67 | severity: severity 68 | ip: l.forwardedFor #l.remoteAddress 69 | #elapsedTime: l.responseTime # e.g., response time for http or method running time for profiling functions 70 | object: l # recording original message in full 71 | options 72 | 73 | super @name, @formatter 74 | # hooking up into Connect middleware 75 | WebApp.connectHandlers.use @httpLogger 76 | 77 | 78 | 79 | 80 | 81 | (exports ? this).Observatory = Observatory -------------------------------------------------------------------------------- /tests/tests/test.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.4.0 2 | (function() { 3 | var Fiber, require, should; 4 | 5 | if (Meteor.isServer) { 6 | require = typeof Npm !== "undefined" && Npm !== null ? Npm.require : __meteor_bootstrap__.require; 7 | Fiber = require('fibers'); 8 | } else { 9 | Fiber = function(fn) { 10 | return { 11 | run: function() { 12 | return fn(); 13 | } 14 | }; 15 | }; 16 | } 17 | 18 | should = chai.should(); 19 | 20 | describe('TLog class', function() { 21 | var tl; 22 | tl = TLog.getLogger(); 23 | if (Meteor.isServer) { 24 | TLog._clear(); 25 | } 26 | it('should be visible, have the global logs collection and log http by default', function() { 27 | TLog.should.exist; 28 | TLog._global_logs.should.exist; 29 | return TLog._log_http.should.be["true"]; 30 | }); 31 | it('should return the default logger with correct defaults', function() { 32 | tl.should.exist; 33 | tl.should.be.an["instanceof"](TLog); 34 | tl._currentLogLevel.should.equal(TLog.LOGLEVEL_DEBUG); 35 | tl._log_user.should.be["true"]; 36 | return tl._printToConsole.should.be["false"]; 37 | }); 38 | return describe('Logging methods:', function() { 39 | it("should call methods with all log levels", function() { 40 | var i, m, _i, _j, _len, _len1, _ref, _ref1, _results; 41 | _ref = ['fatal', 'error', 'warn', 'info', 'verbose', 'debug', 'insaneVerbose']; 42 | for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { 43 | m = _ref[i]; 44 | tl.should.respondTo(m); 45 | tl[m]("Logging " + TLog.LOGLEVEL_NAMES[i] + " message", "TESTS"); 46 | } 47 | _ref1 = ['trace', 'dir']; 48 | _results = []; 49 | for (i = _j = 0, _len1 = _ref1.length; _j < _len1; i = ++_j) { 50 | m = _ref1[i]; 51 | tl.should.respondTo(m); 52 | _results.push(tl[m](new Error("Test Error"), "Message for " + m)); 53 | } 54 | return _results; 55 | }); 56 | return it('should log the message to the database correctly', function(done) { 57 | var customOptions, f, obj, rec; 58 | obj = { 59 | message: "Logging " + TLog.LOGLEVEL_NAMES[TLog.LOGLEVEL_INFO] + " message", 60 | full_message: "Logging " + TLog.LOGLEVEL_NAMES[TLog.LOGLEVEL_INFO] + " FULL message", 61 | module: "TESTS", 62 | timestamp: new Date, 63 | isServer: Meteor.isServer 64 | }; 65 | customOptions = { 66 | cust1: "cust1", 67 | cust2: 10 68 | }; 69 | rec = null; 70 | f = new Fiber(function() { 71 | return tl._lowLevelLog(TLog.LOGLEVEL_INFO, obj, customOptions, function(err, id) { 72 | if (err) { 73 | throw err; 74 | } 75 | rec = TLog._global_logs.findOne(id); 76 | rec.should.exist; 77 | obj.message.should.equal(rec.message); 78 | obj.full_message.should.equal(rec.full_message); 79 | obj.module.should.equal(rec.module); 80 | obj.timestamp.should.eql(rec.timestamp); 81 | obj.isServer.should.equal(rec.isServer); 82 | rec.customOptions.should.deep.equal(customOptions); 83 | return done(); 84 | }); 85 | }); 86 | return f.run(); 87 | }); 88 | }); 89 | }); 90 | 91 | }).call(this); 92 | -------------------------------------------------------------------------------- /monitoringHooks.coffee: -------------------------------------------------------------------------------- 1 | #require = if Npm? then Npm.require else __meteor_bootstrap__.require 2 | #Fiber = require 'fibers' 3 | 4 | #console.log Meteor.default_server.stream_server 5 | ### 6 | Meteor.userIP = (uid)-> 7 | ret = {} 8 | uid = uid ? Meteor.userId() 9 | if uid? 10 | s = ss for k, ss of Meteor.default_server.sessions when ss.userId is uid 11 | if s 12 | ret.forwardedFor = s.socket?.headers?['x-forwarded-for'] 13 | ret.remoteAddress = s.socket?.remoteAddress 14 | ret 15 | ### 16 | 17 | # Ok, we can register handler for every new connecting socket, we can analyze all current meteor sessions 18 | #Meteor.default_server.stream_server.register (socket)-> 19 | ### 20 | console.log "SOCKET Connect! ----------------------------->" 21 | socket.on 'data', (raw_msg)-> 22 | console.log 'Got message in a socket: --------->', @id 23 | console.log raw_msg 24 | socket.on 'close', -> 25 | console.log "Closing socket #{@id}" 26 | 27 | console.dir socket 28 | console.log "METEOR SESSION: ----------------------------->" 29 | Meteor.userIP() 30 | console.dir socket.meteor_session 31 | #console.log s.meteor_session.userId, s.meteor_session.socket.headers for s in Meteor.default_server.stream_server.open_sockets when s.meteor_session? 32 | ### 33 | ### 34 | Meteor.methods 35 | 36 | # TODO: add authorization! 37 | _observatoryGetOpenSockets: -> 38 | #console.log "======================================== called observatoryGetOpenSockets ======================================" 39 | #console.log Meteor.default_server.stream_server.open_sockets 40 | #console.log this 41 | ret = [] 42 | #console.dir Meteor.default_server?.stream_server?.open_sockets 43 | for k, socket of Meteor.default_server?.stream_server?.open_sockets #Meteor.default_server.sessions #Meteor.default_server?.stream_server?.open_sockets 44 | # TODO: DO NOT delete the below as if Meteor internal API changes we'll need to look at it again!!! 45 | # console.dir os.collectionViews 46 | os = socket._meteorSession 47 | #console.dir socket 48 | 49 | # analyzing named subscriptions 50 | ns = {} 51 | for k1, v1 of os._namedSubs 52 | ns[k1] = 53 | uid: v1.userId 54 | name: v1._name 55 | deactivated: v1._deactivated 56 | isReady: v1._ready 57 | params: v1._params 58 | for k,v of v1 59 | if typeof(v) is 'function' 60 | ns[k1][k] = v.toString() 61 | 62 | o = 63 | ddpVersion: os.version 64 | sessionId: os.id 65 | initialized: os.initialized 66 | lastConnect: new Date os.last_connect_time 67 | lastDetach: new Date os.last_detach_time 68 | blocked: os.blocked 69 | workerRunning: os.worker_running 70 | userId: os.userId 71 | sessionData: os.sessionData 72 | namedSubs: ns 73 | 74 | collectionViews: ({name: v.collectionName, id: k, docNumber: Object.keys(v.documents).length} for k,v of os.collectionViews) 75 | 76 | headers: os.socket?.headers 77 | protocol: os.socket?.protocol 78 | address: os.socket?.address 79 | remoteAddress: os.socket?.remoteAddress 80 | remotePort: os.socket?.remotePort 81 | 82 | isSending: os._isSending 83 | pendingReady: os._pendingReady 84 | 85 | ret.push o 86 | #console.dir o 87 | #console.dir os.socket._events 88 | #console.dir Meteor.default_server.sessions 89 | ret 90 | 91 | 92 | ### -------------------------------------------------------------------------------- /lib/server/MonitoringEmitter.coffee: -------------------------------------------------------------------------------- 1 | util = Npm.require 'util' 2 | os = Npm.require 'os' 3 | 4 | Observatory = @Observatory ? {} 5 | 6 | class Observatory.MonitoringEmitter extends @Observatory.MessageEmitter 7 | # doesn't belong here!!! 8 | sysInfo: -> 9 | o = 10 | cpus: os.cpus() 11 | host: os.hostname() 12 | os: 13 | type: os.type() 14 | platform: os.platform() 15 | arch: os.arch() 16 | release: os.release() 17 | network: os.networkInterfaces() 18 | url: Meteor.absoluteUrl() 19 | 20 | sysInfoShort: -> 21 | o = @sysInfo() 22 | o.cpuType = o.cpus[0]?.model 23 | o.cpus = o.cpus.length 24 | o.network = _.keys(o.network).length 25 | o 26 | 27 | measure: -> 28 | obj = 29 | procMemUse: process.memoryUsage() 30 | osUptime: os.uptime() 31 | procUptime: process.uptime() 32 | loadavg: os.loadavg() 33 | totalmem: os.totalmem() 34 | freemem: os.freemem() 35 | 36 | 37 | secondsToString = (seconds) -> 38 | numdays = Math.floor(seconds / 86400) 39 | numhours = Math.floor((seconds % 86400) / 3600) 40 | numminutes = Math.floor(((seconds % 86400) % 3600) / 60) 41 | numseconds = ((seconds % 86400) % 3600) % 60 42 | numdays + " days " + numhours + " hours " + numminutes + " minutes " + numseconds + " seconds" 43 | 44 | 45 | constructor: (@name)-> 46 | # Map of the current sessions 47 | @name = name ? 'Monitor' 48 | @_sessions = [] 49 | @isRunning = false 50 | @_monitorHandle = null 51 | @mi = new Observatory.MeteorInternals 52 | # collection for storing non persistent monitoring events for publishing 53 | # when a client is connected 54 | @Monitors = new Mongo.Collection null 55 | super @name 56 | 57 | # Starting the monitoring process with timePeriod 58 | # Restarts in case it's already running 59 | # TODO: write the actual logic 60 | startMonitor: (timePeriod)-> 61 | #console.log "starting monitor" 62 | @stopMonitor if @isRunning 63 | timePeriod = timePeriod ? 60000 64 | @_monitorHandle = Meteor.setInterval => 65 | obj = @measure() 66 | obj.currentSessionNumber = @mi.getSessionCount() 67 | #console.dir obj 68 | msg = 69 | isServer: true 70 | timestamp: new Date 71 | module: 'Monitor' 72 | type: 'monitor' 73 | severity: Observatory.LOGLEVEL.INFO 74 | object: obj 75 | textMessage: "Monitoring every #{timePeriod / 1000}s" 76 | 77 | @emitMessage msg 78 | @isRunning = true 79 | , timePeriod 80 | 81 | # Stopping the monitoring process 82 | stopMonitor: -> 83 | if @isRunning 84 | Meteor.clearInterval @_monitorHandle 85 | @isRunning = false 86 | 87 | 88 | startNonpersistentMonitor: (timePeriod = 5000)-> 89 | @_persistentMonitorHandle = Meteor.setInterval => 90 | o = @measure() 91 | o.currentSessionNumber = @mi.getSessionCount() 92 | o.timestamp = Date.now() 93 | @Monitors.insert o 94 | , timePeriod 95 | 96 | stopNonpersistentMonitor: -> 97 | #console.log "stopping non-persistent" 98 | Meteor.clearInterval @_persistentMonitorHandle 99 | @Monitors.remove {} 100 | 101 | 102 | 103 | # converting session into logging options 104 | sessionToLoggingOptions: (session)-> 105 | o = 106 | timestamp: null 107 | o 108 | 109 | 110 | (exports ? this).Observatory = Observatory -------------------------------------------------------------------------------- /lib/server/Observatory.coffee: -------------------------------------------------------------------------------- 1 | # TODO: Need to define settings and stuff on the SERVER and publish to the client 2 | # the client needs to be a slave 3 | # SERVER 4 | 5 | Observatory = @Observatory ? {} 6 | 7 | # initialize runs all functions that are registered with registerInitFunction with s as arguments 8 | Observatory.initialize = _.wrap Observatory.initialize, (f, s)-> 9 | #s = Meteor.settings?.public?.observatorySettings unless s? 10 | Observatory.settingsController = new Observatory.Settings 11 | s = Observatory.settingsController.currentSettings() unless s? 12 | #console.log s 13 | f.call Observatory, s 14 | 15 | # extending the settings changing function 16 | Observatory.setSettings = _.wrap Observatory.setSettings, (f, s)-> 17 | # calling base function 18 | f.call Observatory, s 19 | ### 20 | @settings.logUser = s.logUser ? @settings.logUser 21 | @settings.logHttp = s?.logHttp ? @settings.logHttp 22 | @settings.logDDP = s?.logDDP ? @settings.logDDP 23 | ### 24 | 25 | # adding meteor-specific initialization 26 | Observatory.registerInitFunction (s)-> 27 | 28 | 29 | # Default settings for loglevel and printToConsole are INFO and false (defined in Galileo). 30 | ### 31 | @settings.logsCollectionName = s?.logsCollectionName ? '_observatory_logs' 32 | @settings.logUser = s?.logUser ? true 33 | @settings.logHttp = s?.logHttp ? true 34 | @settings.logDDP = s?.logDDP ? false 35 | @settings.prohibitAutoPublish = s?.prohibitAutoPublish ? false 36 | @settings.logAnonymous = s?.logAnonymous ? false 37 | ### 38 | 39 | # setting up client / server meteor loggers 40 | #console.log @settings 41 | @_meteorLogger = new Observatory.MeteorLogger 'Meteor Logger', @settingsController.currentSettings().logsCollectionName ? '_observatory_logs' 42 | @subscribeLogger @_meteorLogger 43 | 44 | @meteorServer = new Observatory.Server 45 | @meteorServer.publish() #unless @settings.prohibitAutoPublish 46 | @meteorServer.publishLocal() # basically, only settings 47 | @emitters.DDP = Observatory.DDPEmitter.de 'DDP' 48 | @emitters.DDPConnection = Observatory.DDPConnectionEmitter.de 'DDP Connection' 49 | @emitters.Http = new Observatory.HttpEmitter 'HTTP' 50 | @emitters.Monitor = new Observatory.MonitoringEmitter 'Monitor' 51 | 52 | @settingsController.processSettingsUpdate @settingsController.currentSettings() 53 | 54 | # checking if running on localhost to bypass authorization 55 | @isLocalhost = if Meteor.absoluteUrl(replaceLocalhost:true).indexOf("http://127.0.0.1") is 0 then true else false 56 | #console.log @isLocalhost 57 | 58 | # setting up buffers checks for http and DDP logging 59 | Meteor.setInterval -> 60 | m = Observatory.getMeteorLogger() 61 | m.processBuffer() 62 | , 3000 63 | 64 | 65 | 66 | 67 | #Observatory.initialize() 68 | 69 | ### 70 | if Meteor.isServer 71 | Observatory._meteorLogger.allowInsert = (uid)-> 72 | console.log "Trying to insert for " + uid 73 | true 74 | ### 75 | 76 | 77 | # TESTING!!! 78 | ### 79 | Meteor.publish = _.wrap Meteor.publish, (f)-> 80 | args = _.rest arguments 81 | #console.log args 82 | 83 | name = args[0] 84 | func = args[1] 85 | 86 | args[1] = _.wrap func, (f1)-> 87 | t1 = Date.now() 88 | args1 = _.rest arguments 89 | console.log "Calling publish function #{name} with #{args1}" 90 | ret = f1.apply this, args1 91 | console.log "...executed in #{Date.now()-t1}ms" 92 | ret 93 | 94 | r = f.apply this, args 95 | r 96 | ### 97 | 98 | 99 | (exports ? this).Observatory = Observatory 100 | -------------------------------------------------------------------------------- /lib/server/DDPConnectionEmitter.coffee: -------------------------------------------------------------------------------- 1 | 2 | Observatory = @Observatory ? {} 3 | 4 | class Observatory.DDPConnectionEmitter extends @Observatory.MessageEmitter 5 | @connectionCount: 0 6 | @messageStub: -> 7 | options = 8 | isServer: true 9 | severity: Observatory.LOGLEVEL.VERBOSE 10 | module: "DDP" 11 | timestamp: new Date 12 | options 13 | 14 | @_instance: undefined 15 | 16 | # getter for the instance 17 | @de: => 18 | @_instance?= new Observatory.DDPConnectionEmitter "DDP Connection Emitter" 19 | @_instance 20 | 21 | @SessionsCollection = new Mongo.Collection null 22 | 23 | # TODO: add support for logging this in settings 24 | constructor: (@name, @formatter)-> 25 | #console.log "DDPEmitter::constructor #{name}" 26 | super @name, @formatter 27 | @turnOff() 28 | if Observatory.DDPConnectionEmitter._instance? then throw new Error "Attempted to create another instance of DDPConnectionEmitter and it is a really bad idea" 29 | # registering to listen to connection events with Meteor 30 | Meteor.onConnection (con)=> 31 | # need to call this for sessions support 32 | Observatory.DDPConnectionEmitter.SessionsCollection.insert({connectionId: con.id, started: Date.now()}) 33 | return unless Observatory.DDPConnectionEmitter.de().isOn #and Observatory.settings.logDDP 34 | Observatory.DDPConnectionEmitter.connectionCount++ 35 | msg = Observatory.DDPConnectionEmitter.messageStub() 36 | msg.connectionId = con.id 37 | msg.textMessage = "New connection #{con.id} from #{con.clientAddress}" 38 | msg.IP = con.clientAddress 39 | msg.object = headers: con.httpHeaders, totalConnections: Observatory.DDPConnectionEmitter.connectionCount 40 | msg.type = "DDPConnection:OPEN" 41 | #msg.userId = @userId() 42 | # emitting message and putting to the buffer for the sake of Meteor logging. Insensitive loggers, such as Console, 43 | # should actually ignore this 44 | #console.log "Sessions: #{Observatory.MeteorInternals.getSessionCount()}" 45 | #console.log "Connections: #{Observatory.DDPConnectionEmitter.connectionCount}" 46 | Observatory.DDPConnectionEmitter.de().emitMessage msg, false 47 | 48 | 49 | 50 | con.onClose => 51 | #console.log "Closing connection #{con.id}" 52 | # need to call this for sessions support 53 | Observatory.DDPConnectionEmitter.SessionsCollection.remove({connectionId: con.id}) 54 | return unless Observatory.DDPConnectionEmitter.de().isOn #and Observatory.settings.logDDP 55 | Observatory.DDPConnectionEmitter.connectionCount-- 56 | msg = Observatory.DDPConnectionEmitter.messageStub() 57 | msg.connectionId = con.id 58 | msg.textMessage = "Closing connection #{con.id} from #{con.clientAddress}" 59 | msg.IP = con.clientAddress 60 | msg.object = totalConnections: Observatory.DDPConnectionEmitter.connectionCount 61 | msg.type = "DDPConnection:CLOSE" 62 | #msg.userId = @userId() 63 | # emitting message and putting to the buffer for the sake of Meteor logging. Insensitive loggers, such as Console, 64 | # should actually ignore this 65 | # console.dir msg 66 | #console.dir Observatory.MeteorInternals.getSessionCount() 67 | #console.log "Sessions: #{Observatory.MeteorInternals.getSessionCount()}" 68 | #console.log "Connections: #{Observatory.DDPConnectionEmitter.connectionCount}" 69 | Observatory.DDPConnectionEmitter.de().emitMessage msg, false 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | (exports ? this).Observatory = Observatory -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | [ 4 | "accounts-base", 5 | "1.1.1" 6 | ], 7 | [ 8 | "accounts-password", 9 | "1.0.2" 10 | ], 11 | [ 12 | "application-configuration", 13 | "1.0.2" 14 | ], 15 | [ 16 | "autoupdate", 17 | "1.1.1" 18 | ], 19 | [ 20 | "base64", 21 | "1.0.0" 22 | ], 23 | [ 24 | "binary-heap", 25 | "1.0.0" 26 | ], 27 | [ 28 | "blaze", 29 | "2.0.1" 30 | ], 31 | [ 32 | "blaze-tools", 33 | "1.0.0" 34 | ], 35 | [ 36 | "boilerplate-generator", 37 | "1.0.0" 38 | ], 39 | [ 40 | "callback-hook", 41 | "1.0.0" 42 | ], 43 | [ 44 | "check", 45 | "1.0.1" 46 | ], 47 | [ 48 | "coffeescript", 49 | "1.0.3" 50 | ], 51 | [ 52 | "ddp", 53 | "1.0.9" 54 | ], 55 | [ 56 | "deps", 57 | "1.0.4" 58 | ], 59 | [ 60 | "ejson", 61 | "1.0.3" 62 | ], 63 | [ 64 | "email", 65 | "1.0.3" 66 | ], 67 | [ 68 | "fastclick", 69 | "1.0.0" 70 | ], 71 | [ 72 | "follower-livedata", 73 | "1.0.1" 74 | ], 75 | [ 76 | "geojson-utils", 77 | "1.0.0" 78 | ], 79 | [ 80 | "html-tools", 81 | "1.0.1" 82 | ], 83 | [ 84 | "htmljs", 85 | "1.0.1" 86 | ], 87 | [ 88 | "http", 89 | "1.0.6" 90 | ], 91 | [ 92 | "id-map", 93 | "1.0.0" 94 | ], 95 | [ 96 | "jquery", 97 | "1.0.0" 98 | ], 99 | [ 100 | "json", 101 | "1.0.0" 102 | ], 103 | [ 104 | "livedata", 105 | "1.0.10" 106 | ], 107 | [ 108 | "localstorage", 109 | "1.0.0" 110 | ], 111 | [ 112 | "logging", 113 | "1.0.3" 114 | ], 115 | [ 116 | "meteor", 117 | "1.1.1" 118 | ], 119 | [ 120 | "meteor-platform", 121 | "1.1.1" 122 | ], 123 | [ 124 | "minifiers", 125 | "1.1.0" 126 | ], 127 | [ 128 | "minimongo", 129 | "1.0.3" 130 | ], 131 | [ 132 | "mobile-status-bar", 133 | "1.0.0" 134 | ], 135 | [ 136 | "mongo", 137 | "1.0.6" 138 | ], 139 | [ 140 | "npm-bcrypt", 141 | "0.7.7" 142 | ], 143 | [ 144 | "observe-sequence", 145 | "1.0.2" 146 | ], 147 | [ 148 | "ordered-dict", 149 | "1.0.0" 150 | ], 151 | [ 152 | "random", 153 | "1.0.0" 154 | ], 155 | [ 156 | "reactive-dict", 157 | "1.0.3" 158 | ], 159 | [ 160 | "reactive-var", 161 | "1.0.2" 162 | ], 163 | [ 164 | "reload", 165 | "1.1.0" 166 | ], 167 | [ 168 | "retry", 169 | "1.0.0" 170 | ], 171 | [ 172 | "routepolicy", 173 | "1.0.1" 174 | ], 175 | [ 176 | "service-configuration", 177 | "1.0.1" 178 | ], 179 | [ 180 | "session", 181 | "1.0.2" 182 | ], 183 | [ 184 | "sha", 185 | "1.0.0" 186 | ], 187 | [ 188 | "spacebars", 189 | "1.0.2" 190 | ], 191 | [ 192 | "spacebars-compiler", 193 | "1.0.2" 194 | ], 195 | [ 196 | "srp", 197 | "1.0.0" 198 | ], 199 | [ 200 | "standard-app-packages", 201 | "1.0.2" 202 | ], 203 | [ 204 | "templating", 205 | "1.0.7" 206 | ], 207 | [ 208 | "tracker", 209 | "1.0.2" 210 | ], 211 | [ 212 | "ui", 213 | "1.0.3" 214 | ], 215 | [ 216 | "underscore", 217 | "1.0.0" 218 | ], 219 | [ 220 | "url", 221 | "1.0.0" 222 | ], 223 | [ 224 | "webapp", 225 | "1.1.2" 226 | ], 227 | [ 228 | "webapp-hashing", 229 | "1.0.0" 230 | ] 231 | ], 232 | "pluginDependencies": [], 233 | "toolVersion": "meteor-tool@1.0.33", 234 | "format": "1.0" 235 | } -------------------------------------------------------------------------------- /lib/server/Settings.coffee: -------------------------------------------------------------------------------- 1 | Observatory = @Observatory ? {} 2 | 3 | #console.dir Observatory.SettingsCommon 4 | # Settings 5 | class Observatory.Settings extends Observatory.SettingsCommon 6 | 7 | @defaultServerSettings: 8 | logLevel: "INFO", printToConsole: false, logUser: true, logAnonymous: false, 9 | logHttp: true, logDDP: false, logBasicDDP: true, prohibitAutoPublish: false 10 | 11 | constructor: -> 12 | #console.log "constructor called" 13 | @col = Observatory.SettingsCommon.col 14 | 15 | # quick-n-dirty protection for read-only stuff - for DEMO etc 16 | # TODO: needs proper checking 17 | ### 18 | @col.deny 19 | insert: -> true 20 | update: -> true 21 | remove: -> true 22 | ### 23 | 24 | @col.allow 25 | insert: (uid, doc) -> Observatory.canRun(uid) 26 | update: (uid, doc, fields, modifier) -> Observatory.canRun(uid) 27 | # TODO: for removal, need to make sure SERVER, CLIENT and ANONYMOUS can't be deleted 28 | remove: (uid, doc) -> Observatory.canRun(uid) and doc.type not in ["SERVER", "CLIENT_LOGGEDIN", "CLIENT_ANONYMOUS"] 29 | #console.log this 30 | # every startup checking that the databse contains at least the key stuff 31 | @loadSettings() 32 | # observing if our SERVER values change so that we process these changes 33 | @col.find({type: "SERVER"}).observe { 34 | changed: (newDoc, oldDoc)=> 35 | @processSettingsUpdate(newDoc.settings) 36 | 37 | removed: (doc)=> 38 | throw new Meteor.Error 78, "SERVER settings removed: this SHOULD NOT be happening!" 39 | } 40 | 41 | needsSetup: -> 42 | if @col.find({initialSetupComplete: true}).count()>0 then false else true 43 | setupComplete: -> 44 | @col.insert({initialSetupComplete: true}) 45 | 46 | loadSettings: -> 47 | # first run in the app - filling collection with defaults (initial setup) 48 | if @col.find().count() is 0 49 | @col.insert({type: "SERVER", settings: Observatory.Settings.defaultServerSettings}) 50 | @col.insert({type: "CLIENT_LOGGEDIN", settings: Observatory.Settings.defaultClientSettings}) 51 | @col.insert({type: "CLIENT_ANONYMOUS", settings: Observatory.Settings.defaultClientSettings}) 52 | 53 | if @col.find({type: "SERVER"}).count() is 0 54 | @col.insert({type: "SERVER", settings: Observatory.Settings.defaultServerSettings}) 55 | 56 | @currentSettings() 57 | 58 | publishLocal: -> 59 | Meteor.publish '_observatory_settings', (opts)-> 60 | #console.log 'publishing settings' 61 | # for now, no granularity, only anon vs logged in 62 | if @userId then Observatory.SettingsCommon.col.find {type: "CLIENT_LOGGEDIN"} else Observatory.SettingsCommon.col.find {type: "CLIENT_ANONYMOUS"} 63 | 64 | publishAdmin: -> 65 | # TODO: rethink naming, as now Vega won't be able to monitor itself on the client (maybe that's ok) 66 | Meteor.publish '_observatory_settings_admin', (opts)-> 67 | #console.log @ 68 | #console.log "publishing settings #{@userId}" 69 | return if not Observatory.canRun.call(@) 70 | Observatory.SettingsCommon.col.find {} 71 | 72 | # returns current settings relevant for the environment in which it's called 73 | currentSettings: -> 74 | cs = @col.findOne({type: "SERVER"}) 75 | #console.log cs 76 | cs.settings 77 | 78 | processSettingsUpdate: (s)-> 79 | # printToConsole and loglevel are handled by super() call 80 | super s 81 | if s.logBasicDDP 82 | Observatory.emitters.DDPConnection.turnOn() 83 | else 84 | Observatory.emitters.DDPConnection.turnOff() 85 | if s.logDDP 86 | Observatory.emitters.DDP.turnOn() 87 | else 88 | Observatory.emitters.DDP.turnOff() 89 | if s.logHttp 90 | Observatory.emitters.Http.turnOn() 91 | else 92 | Observatory.emitters.Http.turnOff() 93 | 94 | # anonymous & client calls in general 95 | # this is somewhat dangerous as anonymous users can mess up the collection (but only inserting stuff, so not really) 96 | if @currentSettings().logAnonymous 97 | Observatory._meteorLogger.allowInsert = -> true 98 | else 99 | if @currentSettings().logUser 100 | Observatory._meteorLogger.allowInsert = (uid) -> if uid? then true else false 101 | else 102 | Observatory._meteorLogger.allowInsert = -> false 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | ###################################################################################################### 113 | # Settings changing functions 114 | ###################################################################################################### 115 | 116 | (exports ? this).Observatory = Observatory -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | What is it? 2 | ------------- 3 | This is Observatory v0.4.8 - package that provides powerful, efficient 4 | and pretty logging, monitoring and application management for [Meteor framework](http://meteor.com) application development and 5 | deployment. 6 | [See it in action and read full usage docs!](http://observatoryjs.com/) 7 | 8 | What does it do? 9 | ------------------ 10 | * Easy logging with different log levels with corresponding methods for message output, optional 11 | logging to console, pretty output of both Client and Server logs right in the browser, logging of 12 | the currently logged-in user for additional control. 13 | 14 | * Augomagical logging, profiling and error handling for DDP, http, Collections, Subscriptions, Template lifecycle methods and any custom code 15 | 16 | * Monitoring of your application internals: publish and methods handlers, active sessions, etc 17 | 18 | * Full-featured cloud-based monitoring and management of your Meteor applications: closed Alpha! 19 | 20 | Installation 21 | ----------------- 22 | #### As a Meteor package: 23 | 24 | meteor add superstringsoft:observatory 25 | 26 | Usage 27 | --------- 28 | 29 | tb = Observatory.getToolbox() 30 | tb.warn("This is a warning message") 31 | 32 | There's *much* more. 33 | [Read full docs](http://observatoryjs.com) and sign up to be notified for the cloud launch! 34 | 35 | 36 | Feedback 37 | ---------- 38 | We'd love to hear what you think, whether it's useful and which other features you'd want to see -- so please submit issues here on github or [leave a comment on our blog](http://meteorology.io) 39 | to share your thoughts and ideas! 40 | 41 | Revision history 42 | ----------------- 43 | ####0.4.8: October, 4, 2014 44 | * Heartbeat monitors published 45 | * Code DRYed in publishes 46 | * Publishing logs based on time span instead of counts 47 | * Monitors record online sessions 48 | 49 | ####0.4.7: October, 2, 2014 50 | * Authorization improvements: localhost does not require one, graceful handling on the client 51 | 52 | ####0.4.6: September, 29, 2014 53 | * Visual wizards and management options: all current settings can be managed via UI now 54 | * API setup for advanced monitoring support 55 | 56 | ####0.4.5: September, 26, 2014 57 | * Major code improvements 58 | * Authentication 59 | * Session monitoring support 60 | 61 | ####0.4.0: September, 24, 2014 62 | * Bump to Meteor 0.9 & some re-architecturing 63 | 64 | ####0.3.2: September, 14, 2013 65 | * Additional enhancements for cloud support: 66 | * Versioning 67 | * Handshakes 68 | * Heartbeats 69 | * Profiler methods, minor bug fixes in Galileo 70 | * Better & profiled automagical subscription logging 71 | 72 | ####0.3.0: September, 10, 2013 73 | * Completely new modular architecture, based on Meteor-independent coffee-script 74 | * Added monitoring, profiling and alpha automagical logging for Collections, Subscriptions and Templates 75 | * Backward-compatible 76 | 77 | ####0.2.7: August, 31, 2013 78 | * DDP server logging added 79 | * Bug fixes in the client monitoring part 80 | * Got rid of dependency on the bootstrap, fixed main panel, session needs more work 81 | 82 | ####0.2.6: August, 28, 2013 83 | * Added Meteor.settings support 84 | * Better user logging options 85 | * Weak dependency on the bootstrap 86 | 87 | ####0.2.53: August, 19, 2013 88 | * Updated to work with Meteor 0.6.5 89 | 90 | ####0.2.1: March 25, 2013 91 | * Added http requests logging via connect middleware hook 92 | * Changed UI behavior so that Observatory modifies last child of <body> to be able to scroll main site content up 93 | * Preparation for Meteor 0.6.0 in terms of variables scoping 94 | * Internal package restructuring - moved core logger etc to a separate package (to be released separately soon) 95 | 96 | ####0.2.0: March 18, 2013 97 | First minor release with "real" Meteor (auth, users, ui improvements). 98 | * Properly logging currently logged-in user 99 | * Works with Meteor 0.5.9 100 | * Code highlighting in the Templates Internals 101 | * Session as a separate panel 102 | * Unit testing moved to a [separate package](https://github.com/superstringsoftware/observatory-testing) 103 | * Keyboard based control of the panels ("~" and "ctrl + ~") 104 | * Setting default panel states via Session 105 | 106 | ####0.1.9: March 6, 2013 107 | Update to support Meteor 0.5.7: 108 | * New logging methods (debug, trace, dir) 109 | * Meteor Authorization support 110 | * Some clean up of the logs display in the panel 111 | * Fixing template methods inspection for the new Meteor syntax 112 | * Preview version of unit testing 113 | 114 | ####0.1.8: Oct, 2012 115 | Minor code clean up, ability to clear logs. 116 | 117 | ####0.1.7 118 | Some styling changes, module names support: 119 | * added 2 themes: dark and light, switchable on the fly. 120 | * added module names support to easier sort through logs. 121 | * some css cleanup, started restyling to get away from Bootstrap dependency (still dependent though so if you use css names that 122 | are the same as Twitter Bootstrap is using you may get weird effects in your app - will remove in a future version). 123 | 124 | ####0.1.5, 0.1.6 125 | Bottom sliding panel with tabs added, monitoring / inspection capabilities added, files renamed, some cleanup started 126 | 127 | ####0.1.1 128 | Clean up for visibility / encapsulation, now need to use TLog.getLogger() instead of constructing directly 129 | 130 | ####0.1 131 | Initial commit 132 | 133 | -------------------------------------------------------------------------------- /lib/client/templates.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | Template.newPost.rendered = _.wrap Template.newPost.rendered, (func)-> 3 | console.log "Injection successful!!!" 4 | console.log this 5 | func.apply this 6 | ### 7 | 8 | Observatory = @Observatory ? {} 9 | 10 | _.extend Observatory, 11 | 12 | getTemplateNames: (includeHidden = false)-> 13 | ret = [] 14 | for k,v of Template 15 | ret.push k unless k.indexOf('_') is 0 16 | ret 17 | 18 | getTemplate: (name)->_.find Template, (k,v)-> v is name 19 | 20 | getEvents: (name)-> Template[name]?.__eventMaps 21 | getHelpers: (name)-> @getTemplate(name)?._tmpl_data.helpers 22 | 23 | logAll: -> 24 | @logTemplates() 25 | @logMeteor() 26 | 27 | logTemplates: -> 28 | console.log "logging templates now" 29 | names = @getTemplateNames() 30 | console.log names 31 | callbacks = ['created','rendered','destroyed'] 32 | tl = Observatory.getToolbox() 33 | Observatory.ot = {} 34 | ### 35 | for t in names 36 | Observatory.ot[t] = {} 37 | for c in callbacks 38 | if Template[t][c]? 39 | #console.log Template[t][c].toString() 40 | Observatory.ot[t][c] = Template[t][c] 41 | #_.bind oldf, Template[t] 42 | #console.log "For #{t}.#{c}():" 43 | #console.log ot 44 | 45 | Template[t][c] = _.wrap Observatory.ot[t][c], (f)-> 46 | tl = Observatory.getToolbox() 47 | tl.debug "#{c}() call started", "Template.#{t}" 48 | time1 = Date.now() 49 | #console.dir Observatory.ot 50 | #console.dir this 51 | #f.call this 52 | time2 = Date.now() - time1 53 | tl.profile "#{c}() call finished in #{time2} ms", time2, {template: t, type: 'template'} 54 | #ret 55 | #tl.debug "#{c} call finished", "Template.#{t}" 56 | 57 | console.dir Observatory.ot 58 | ### 59 | 60 | 61 | logCollection: -> 62 | sync = ['find','findOne'] 63 | async = ['insert','update','remove'] 64 | #console.log Meteor.Collection:: 65 | 66 | # Ok, can't call insert etc inside collection methods - problem is, TLog collection 67 | # is created from Collection as well, so it goes crazy. Need to make sure we are not 68 | # applying this to TLog, but not sure how so far -- 69 | # One option is just store this stuff in the buffer, as with http logs 70 | # and then process it, checking when processing not to add the logs if it's about TLog 71 | for m in sync 72 | Meteor.Collection::[m] = _.wrap Meteor.Collection::[m], (f)-> 73 | console.log "#{m} call started", "Collection.#{@_name}" 74 | console.log arguments 75 | ret = f.apply this, _.rest(arguments) 76 | console.log "#{m} call finished", "Collection.#{@_name}" 77 | ret 78 | 79 | 80 | # for now, only subscribe 81 | logMeteor: -> 82 | console.log "logging Meteor - CALLED ALL DYNAMIC STUFF" 83 | #console.log Meteor.subscribe 84 | 85 | Meteor.subscribe = _.wrap Meteor.subscribe, (f)-> 86 | #console.log "hmm... subscribe" 87 | #console.log arguments 88 | tl = Observatory.getToolbox() 89 | name = arguments[1] 90 | #console.log Observatory.settings.maxSeverity 91 | 92 | # some funky stuff to wrap original callbacks 93 | last = _.last arguments 94 | changeLast = false 95 | if typeof last is 'object' 96 | if last.onReady? 97 | origOnReady = last.onReady 98 | changeLast = true 99 | if last.onError? 100 | origOnError = last.onError 101 | changeLast = true 102 | else 103 | if typeof last is 'function' 104 | origOnReady = last 105 | changeLast = true 106 | 107 | cb = {} 108 | if origOnReady? 109 | cb.onReady = _.wrap origOnReady, (f)-> 110 | #console.log "OnReady callback" 111 | #console.log arguments 112 | t = Date.now() - Session.get "_obs.subscription.#{name}.profileStart" 113 | tl._profile "Subscription ready for #{name} in #{t} ms", t, {subscription: name, type: 'subscription'} 114 | args = _.rest arguments 115 | f.apply @, args 116 | else 117 | cb.onReady = -> 118 | #console.log "OnReady callback no arguments" 119 | t = Date.now() - Session.get "_obs.subscription.#{name}.profileStart" 120 | tl._profile "Subscription ready for #{name} in #{t} ms", t, {subscription: name, type: 'subscription'} 121 | 122 | if origOnError? 123 | cb.onError = _.wrap origOnError, (f)-> 124 | #console.log "OnError callback" 125 | #console.log arguments 126 | t = Date.now() - Session.get "_obs.subscription.#{name}.profileStart" 127 | tl._error "Error while subscribing to #{name}: " + err.reason, {error: err, subscription: name, timeElapsed: t, type: 'subscription'} 128 | args = _.rest _.rest arguments 129 | f.apply @, args 130 | else 131 | cb.onError = -> 132 | #console.log "OnError callback no arguments" 133 | t = Date.now() - Session.get "_obs.subscription.#{name}.profileStart" 134 | tl._error "Error while subscribing to #{name}: " + err.reason, {error: err, subscription: name, timeElapsed: t, type: 'subscription'} 135 | 136 | args = _.rest arguments 137 | 138 | if changeLast then args[args.length - 1] = cb # replacing original callbacks 139 | else args.push cb # adding callbacks 140 | 141 | #console.log args 142 | 143 | Session.set "_obs.subscription.#{name}.profileStart", Date.now() 144 | tl._verbose "Subscribing to #{name}", "Meteor" 145 | f.apply this, args 146 | 147 | 148 | ### 149 | Meteor.call = _.wrap Meteor.call, (f)-> 150 | console.log "hmm... call" 151 | console.log arguments 152 | tl = Observatory.getToolbox() 153 | name = arguments[1] 154 | #console.log Observatory.settings.maxSeverity 155 | 156 | # some funky stuff to wrap original callbacks 157 | last = _.last arguments 158 | changeLast = false 159 | if typeof last is 'function' 160 | origOnReady = last 161 | changeLast = true 162 | cb = _.wrap origOnReady, (f1)-> 163 | console.log "OnReady callback" 164 | console.log arguments 165 | t = Date.now() - Session.get "_obs.methodCall.#{name}.profileStart" 166 | tl._profile "Method #{name} executed in #{t} ms", t, {method: name, type: 'method'} 167 | args = _.rest arguments 168 | f1.apply @, args 169 | else 170 | cb = (err, res)-> 171 | console.log "OnReady callback no arguments" 172 | console.log err 173 | console.log res 174 | t = Date.now() - Session.get "_obs.methodCall.#{name}.profileStart" 175 | tl._profile "Method #{name} executed in #{t} ms", t, {method: name, type: 'method'} 176 | 177 | args = _.rest arguments 178 | 179 | if changeLast then args[args.length - 1] = cb # replacing original callbacks 180 | else args.push cb # adding callbacks 181 | 182 | #console.log args 183 | 184 | Session.set "_obs.methodCall.#{name}.profileStart", Date.now() 185 | tl._verbose "Starting execution of #{name}", "Meteor" 186 | console.dir @ 187 | console.dir args 188 | f.apply this, args 189 | 190 | 191 | Meteor.apply = _.wrap Meteor.apply, (f)-> 192 | console.log "hmm... apply" 193 | console.log arguments 194 | tl = Observatory.getToolbox() 195 | name = arguments[1] 196 | origArgs = arguments[2] 197 | #console.log Observatory.settings.maxSeverity 198 | 199 | # some funky stuff to wrap original callbacks 200 | last = _.last arguments 201 | changeLast = false 202 | if typeof last is 'function' 203 | origOnReady = last 204 | changeLast = true 205 | 206 | cb = => 207 | t = Date.now() - Session.get "_obs.methodCall.#{name}.profileStart" 208 | tl._profile "Method #{name} executed in #{t} ms", t, {method: name, type: 'method'} 209 | origOnReady() if origOnReady? # TODO: what about the arguments??? 210 | 211 | args = _.rest arguments 212 | 213 | if changeLast then args[args.length - 1] = cb # replacing original callbacks 214 | else args.push cb # adding callbacks 215 | 216 | #console.log args 217 | 218 | Session.set "_obs.methodCall.#{name}.profileStart", Date.now() 219 | tl._verbose "Starting execution of #{name}", "Meteor" 220 | f.apply this, args 221 | ### 222 | 223 | 224 | (exports ? this).Observatory = Observatory -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | ### Preamble: 2 | 3 | This Agreement governs the relationship between the user (hereinafter: Licensee) and Silver Sales Development LTD (Hereinafter: Licensor). This Agreement sets the terms, rights, restrictions and obligations on using Observatory-Apollo plugin (hereinafter: The Software) created and owned by Licensor, as detailed herein 4 | 5 | ### License Grant: 6 | 7 | Licensor hereby grants Licensee a Personal, Non-assignable & non-transferable, Commercial, Royalty free, Including the rights to create but not distribute derivative works, Non-exclusive license, all with accordance with the terms set forth and other legal restrictions set forth in 3rd party software used while running Software. 8 | Limited: Licensee may use Software for the purpose of: 9 | Running Software on Licensee’s Website[s] and Server[s]; 10 | Allowing 3rd Parties to run Software on Licensee’s Website[s] and Server[s]; 11 | Publishing Software’s output to Licensee and 3rd Parties; 12 | Distribute verbatim copies of Software’s output (including compiled binaries); 13 | Modify Software to suit Licensee’s needs and specifications. 14 | Binary Restricted: Licensee may sublicense Software as a part of a larger work containing more than Software, distributed solely in Object or Binary form under a personal, non-sublicensable, limited license. Such redistribution shall be limited to {apps} codebases. 15 | Non Assignable & Non-Transferable: Licensee may not assign or transfer his rights and duties under this license. 16 | Commercial, Royalty Free: Licensee may use Software for any purpose, including paid-services, without any royalties 17 | Including the Right to Create Derivative Works: Licensee may create derivative works based on Software, including amending Software’s source code, modifying it, integrating it into a larger work or removing portions of Software, as long as no distribution of the derivative works is made 18 | With Attribution Requirements: 19 | Term & Termination: The Term of this license shall be until terminated. Licensor may terminate this Agreement, including Licensee’s license in the case where Licensee : 20 | became insolvent or otherwise entered into any liquidation process; or 21 | exported The Software to any jurisdiction where licensor may not enforce his rights under this agreements in; or 22 | Licenee was in breach of any of this license's terms and conditions and such breach was not cured, immediately upon notification; or 23 | Licensee in breach of any of the terms of clause 2 to this license; or 24 | Licensee otherwise entered into any arrangement which caused Licensor to be unable to enforce his rights under this License. 25 | Payment: In consideration of the License granted under clause 2, Licensee shall pay Licensor a fee, via Credit-Card, PayPal or any other mean which Licensor may deem adequate. Failure to perform payment shall construe as material breach of this Agreement. 26 | Upgrades, Updates and Fixes: Licensor may provide Licensee, from time to time, with Upgrades, Updates or Fixes, as detailed herein and according to his sole discretion. Licensee hereby warrants to keep The Software up-to-date and install all relevant updates and fixes, and may, at his sole discretion, purchase upgrades, according to the rates set by Licensor. Licensor shall provide any update or Fix free of charge; however, nothing in this Agreement shall require Licensor to provide Updates or Fixes. 27 | Upgrades: for the purpose of this license, an Upgrade shall be a material amendment in The Software, which contains new features and or major performance improvements and shall be marked as a new version number. For example, should Licensee purchase The Software under version 1.X.X, an upgrade shall commence under number 2.0.0. 28 | Updates: for the purpose of this license, an update shall be a minor amendment in The Software, which may contain new features or minor improvements and shall be marked as a new sub-version number. For example, should Licensee purchase The Software under version 1.1.X, an upgrade shall commence under number 1.2.0. 29 | Fix: for the purpose of this license, a fix shall be a minor amendment in The Software, intended to remove bugs or alter minor features which impair the The Software's functionality. A fix shall be marked as a new sub-sub-version number. For example, should Licensee purchase Software under version 1.1.1, an upgrade shall commence under number 1.1.2. 30 | Support: Software is provided under an AS-IS basis and without any support, updates or maintenance. Nothing in this Agreement shall require Licensor to provide Licensee with support or fixes to any bug, failure, mis-performance or other defect in The Software. 31 | Bug Notification: Licensee may provide Licensor of details regarding any bug, defect or failure in The Software promptly and with no delay from such event; Licensee shall comply with Licensor's request for information regarding bugs, defects or failures and furnish him with information, screenshots and try to reproduce such bugs, defects or failures. 32 | Feature Request: Licensee may request additional features in Software, provided, however, that (i) Licesee shall waive any claim or right in such feature should feature be developed by Licensor; (ii) Licensee shall be prohibited from developing the feature, or disclose such feature request, or feature, to any 3rd party directly competing with Licensor or any 3rd party which may be, following the development of such feature, in direct competition with Licensor; (iii) Licensee warrants that feature does not infringe any 3rd party patent, trademark, trade-secret or any other intellectual property right; and (iv) Licensee developed, envisioned or created the feature solely by himself. 33 | Liability: To the extent permitted under Law, The Software is provided under an AS-IS basis. Licensor shall never, and without any limit, be liable for any damage, cost, expense or any other payment incurred by Licesee as a result of Software’s actions, failure, bugs and/or any other interaction between The Software and Licesee’s end-equipment, computers, other software or any 3rd party, end-equipment, computer or services. Moreover, Licensor shall never be liable for any defect in source code written by Licensee when relying on The Software or using The Software’s source code. 34 | Warranty: 35 | Intellectual Property: Licensor hereby warrants that The Software does not violate or infringe any 3rd party claims in regards to intellectual property, patents and/or trademarks and that to the best of its knowledge no legal action has been taken against it for any infringement or violation of any 3rd party intellectual property rights. 36 | No-Warranty: The Software is provided without any warranty; Licensor hereby disclaims any warranty that The Software shall be error free, without defects or code which may cause damage to Licensee’s computers or to Licensee, and that Software shall be functional. Licensee shall be solely liable to any damage, defect or loss incurred as a result of operating software and undertake the risks contained in running The Software on License’s Server[s] and Website[s]. 37 | Prior Inspection: Licensee hereby states that he inspected The Software thoroughly and found it satisfactory and adequate to his needs, that it does not interfere with his regular operation and that it does meet the standards and scope of his computer systems and architecture. Licensee found that The Software interacts with his development, website and server environment and that it does not infringe any of End User License Agreement of any software Licensee may use in performing his services. Licensee hereby waives any claims regarding The Software's incompatibility, performance, results and features, and warrants that he inspected the The Software. 38 | No Refunds: Licensee warrants that he inspected The Software according to clause 7(c) and that it is adequate to his needs. Accordingly, as The Software is intangible goods, Licensee shall not be, ever, entitled to any refund, rebate, compensation or restitution for any reason whatsoever, even if The Software contains material flaws. 39 | Indemnification: Licensee hereby warrants to hold Licensor harmless and indemnify Licensor for any lawsuit brought against it in regards to Licensee’s use of The Software in means that violate, breach or otherwise circumvent this license, Licensor's intellectual property rights or Licensor's title in The Software. Licensor shall promptly notify Licensee in case of such legal action and request Licensee’s consent prior to any settlement in relation to such lawsuit or claim. 40 | Governing Law, Jurisdiction: Licensee hereby agrees not to initiate class-action lawsuits against Licensor in relation to this license and to compensate Licensor for any legal fees, cost or attorney fees should any claim brought by Licensee against Licensor be denied, in part or in full. 41 | 42 | 43 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 44 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 45 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 46 | DISCLAIMED. IN NO EVENT SHALL Superstring Software or its contributors BE LIABLE FOR ANY 47 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 48 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 49 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 50 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 51 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 52 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /lib/server/ObservatoryServer.coffee: -------------------------------------------------------------------------------- 1 | Observatory = @Observatory ? {} 2 | 3 | # quick and dirty authorization 4 | # user - currently logged in user or null 5 | # action - name of the action they want to run 6 | # in the future, need some role-based checking, action map etc 7 | # for now - only checking for the administrator role 8 | # to address mind-boggling @userId issue, call as Observatory.canRun.call this !!! in publish functions 9 | Observatory.canRun = (uid, action = 'view')-> 10 | #return true if Observatory.isLocalhost 11 | res = false 12 | if uid? 13 | user = Meteor.users.findOne(uid) 14 | else 15 | try 16 | user = (Meteor.users.findOne(_id: @userId) ? Meteor.user()) if not uid? 17 | catch err 18 | #console.log @userId 19 | #console.log user 20 | res = true if user?.profile?.observatoryProfile?.role is "administrator" 21 | #console.log "Result of running canRun is #{res}" 22 | res 23 | 24 | # Class that publishes logs, manages relations with clients, sets up monitors etc 25 | # heart of Observatory operations in Meteor 26 | class Observatory.Server 27 | 28 | constructor: -> 29 | @mi = new Observatory.MeteorInternals 30 | @monitor = new Observatory.MonitoringEmitter 31 | 32 | # TODO: need to log calls when there's no needsSetup - that's malicious activity!!! 33 | # now adding a new user with administrator priviliges and changing the initialSetupComplete doc in the database 34 | initialSetup: (options)-> 35 | return unless Observatory.settingsController.needsSetup() 36 | {user, email, password} = options 37 | #console.log "#{user}, #{password}, #{email}" 38 | id = Accounts.createUser {username: user, email: email, password: password, profile: {observatoryProfile: {role: "administrator"}} } 39 | Observatory.settingsController.setupComplete() if id? 40 | 41 | 42 | handshake: -> 43 | #console.log Meteor.user() 44 | #console.log Observatory.settingsController 45 | o = 46 | version: Observatory.version 47 | isLocalhost: Observatory.isLocalhost 48 | needsSetup: Observatory.settingsController.needsSetup() 49 | monitoring: Observatory.emitters.Monitor.isRunning 50 | registeredUsers: Meteor.users.find().count() 51 | logsCount: Observatory.getMeteorLogger().logsCount() 52 | meteorVersion: Meteor.release 53 | heartbeat: @heartbeat() 54 | sysinfo: Observatory.emitters.Monitor.sysInfoShort() 55 | 56 | heartbeat: -> 57 | @monitor.measure() 58 | 59 | 60 | # publishing settings and other info to local Observatory clients (local to the App being monitored that is) 61 | # now, of course you can connect to anything published both directly as well as via ddp.connect, 62 | # but keeping this separate for easier maintenance 63 | # TODO: granular settings publishing --> based on userId and connectionId eventually 64 | publishLocal: -> Observatory.settingsController.publishLocal() 65 | 66 | 67 | _publishLogsTimed: (name, collectionName, selector)-> 68 | Meteor.publish name, (hours = 12, scrollback = 0, limit = 2000)-> 69 | return if not Observatory.canRun.call(@) 70 | cl = Observatory.getMeteorLogger()._logsCollection 71 | dt = new Date (Date.now() - 3600000 * hours) 72 | selector.timestamp = $gt: dt 73 | #console.dir selector 74 | handle = cl.find(selector, {sort: {timestamp: -1}, limit: limit}).observe { 75 | added: (doc)=> 76 | @added(collectionName, doc._id, doc) 77 | } 78 | @ready() 79 | @onStop = -> handle.stop() 80 | return 81 | 82 | # func should return whether we allow publishing or not 83 | # This is the heart of Vega operations - publishing all necessary data to the client 84 | publish: (func)-> 85 | # publishing ALL settings for management purposes 86 | Observatory.settingsController.publishAdmin() 87 | 88 | # publishing logs 89 | ### 90 | Meteor.publish '_observatory_logs', (numInPage = 300, pageNumber = 0)-> 91 | #console.log "trying to publish logs with #{numInPage} and #{pageNumber}" 92 | return if not Observatory.canRun.call(@) 93 | #console.log "trying to publish logs with #{numInPage} and #{pageNumber}" 94 | cl = Observatory.getMeteorLogger()._logsCollection 95 | cr = cl.find({type: {$ne: 'monitor'}}, {sort: {timestamp: -1}, limit: numInPage}) 96 | cr 97 | ### 98 | 99 | # funky stuff - publishing specific query, just the monitoring logs 100 | @_publishLogsTimed '_observatory_logs', '_observatory_remote_logs', type: $ne: 'monitor' 101 | @_publishLogsTimed '_observatory_monitoring', '_observatory_monitoring', type: 'monitor' 102 | @_publishLogsTimed '_observatory_http_logs', '_observatory_http_logs', module: 'HTTP' 103 | @_publishLogsTimed '_observatory_errors', '_observatory_errors', severity: $lte: 1 104 | @_publishLogsTimed '_observatory_profiling', '_observatory_profiling', type: 'profile' 105 | 106 | 107 | ######################################################################################################################## 108 | # NON - PERSISTENT PUBLISHES 109 | ######################################################################################################################## 110 | 111 | 112 | # open sessions - see DDPConnectionEmitter for hooks on manipulating dummy SessionsCollection 113 | Meteor.publish '_observatory_current_sessions', -> 114 | return if not Observatory.canRun.call(@) 115 | mi = new Observatory.MeteorInternals 116 | #console.log "trying to publish current sessions" 117 | #initializing = true 118 | handle = Observatory.DDPConnectionEmitter.SessionsCollection.find().observe { 119 | added: (doc)=> 120 | #console.log "adding session to publish!!!", doc 121 | #console.log this 122 | # needs to be here because of mind-driving-crazy @userId thing in Meteor :((( 123 | ss = mi.convertSessionToView mi.findSession(doc.connectionId) 124 | ss.started = doc.started 125 | @added('_observatory_current_sessions', doc.connectionId, ss) #unless initializing 126 | 127 | removed: (doc)=> 128 | @removed('_observatory_current_sessions', doc.connectionId) 129 | } 130 | #initializing = false 131 | @ready() 132 | @onStop = -> handle.stop() 133 | return 134 | 135 | # publishing users in the selected [id] list - useful for getting logged in users etc 136 | Meteor.publish '_observatory_selected_users', (userIds)-> 137 | #console.log userIds 138 | return unless Observatory.canRun.call(@) 139 | userIds = [] if not userIds? 140 | # Adding observatory administrators for additional checks on the client 141 | # (does not breach security because all server calls check if the user can run it in any case - 142 | # the only reason we are doing it is for more graceful handling of logged in users, who are NOT 143 | # Observatory admins) 144 | handle = Meteor.users.find({$or: [ {_id: {$in: userIds}}, {"profile.observatoryProfile.role": "administrator"} ]}, fields: services: 0).observe { 145 | added: (doc)=> 146 | #console.log doc 147 | @added('_observatory_remote_users', doc._id, doc) #unless initializing 148 | 149 | removed: (doc)=> 150 | @removed('_observatory_remote_users', doc._id) 151 | } 152 | #initializing = false 153 | @ready() 154 | @onStop = -> handle.stop() 155 | return 156 | 157 | monitor = @monitor # 'self = this' but don't want to mess with 'this' here 158 | Meteor.publish '_observatory_nonpersistent_monitor', (timePeriod = 5000, dataPoints = 50)-> 159 | return unless Observatory.canRun.call(@) 160 | monitor.stopNonpersistentMonitor() 161 | monitor.startNonpersistentMonitor timePeriod 162 | handle = monitor.Monitors.find({}, sort: {timestamp: -1}).observe { 163 | added: (doc)=> 164 | #console.log "Monitors are ", monitor.Monitors.find({}).count() 165 | @added('_observatory_nonpersistent_monitor', doc._id, doc) #unless initializing 166 | if monitor.Monitors.find({}).count() > dataPoints 167 | #console.log "Monitors too many, cleaning up" 168 | monitor.Monitors.remove timestamp: $lt: (Date.now() - timePeriod * dataPoints) 169 | 170 | removed: (doc)=> 171 | @removed('_observatory_nonpersistent_monitor', doc._id) 172 | } 173 | #initializing = false 174 | @ready() 175 | @onStop = -> 176 | handle.stop() 177 | monitor.stopNonpersistentMonitor() 178 | return 179 | 180 | 181 | 182 | startConnectionMonitoring: -> 183 | 184 | 185 | 186 | 187 | 188 | ################################################################################################################################################ 189 | # METHODS 190 | ################################################################################################################################################# 191 | 192 | Meteor.methods 193 | # No authorization: initial handshake with the server 194 | # TODO: strip it down to Observatory version and essential info to establish a connection 195 | _observatoryHandshake: -> Observatory.meteorServer.handshake() 196 | # Initial (First Time) setup - so, no auth 197 | _observatoryInitialSetup: (options)-> Observatory.meteorServer.initialSetup options 198 | 199 | # METHODS REQUIRING AUTHORIZATION 200 | # Current server - method and publish handlers 201 | # TODO - remove Observatory handlers? 202 | _observatoryGetCurrentServer: -> 203 | throw new Meteor.Error(77,"Observatory access denied") if not Observatory.canRun() 204 | Observatory.meteorServer.mi.getCurrentServer() 205 | 206 | # Regular heartbeat 207 | _observatoryHeartbeat: -> 208 | throw new Meteor.Error(77,"Observatory access denied") if not Observatory.canRun() 209 | Observatory.meteorServer.heartbeat() 210 | 211 | # Currently open sessions 212 | _observatoryGetOpenSessions: -> 213 | throw new Meteor.Error(77,"Observatory access denied") if not Observatory.canRun() 214 | mi = Observatory.meteorServer.mi 215 | ss = mi.getCurrentSessions() 216 | sessions = [] 217 | sessions.push mi.convertSessionToView(v) for k,v of ss 218 | #console.dir v._namedSubs 219 | sessions 220 | 221 | 222 | 223 | # auth stuff 224 | 225 | _checkUserId = -> 226 | #console.log @ 227 | uid = null 228 | try 229 | uid = this.userId ? Meteor.userId() 230 | #console.log uid 231 | return uid 232 | catch err 233 | #console.log err 234 | uid 235 | 236 | (exports ? this).Observatory = Observatory --------------------------------------------------------------------------------