├── .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
--------------------------------------------------------------------------------