29 | 'use strict';
30 |
31 | // app.js - cognicity-reports-powertrack application setup
32 |
33 | /**
34 | * @file Collect unconfirmed reports from Twitter via Gnip PowerTrack & send report verification tweets
35 | * @copyright (c) Tomas Holderness & SMART Infrastructure Facility January 2014
36 | * @license Released under GNU GPLv3 License (see LICENSE.txt).
37 | * @example
38 | * Usage:
39 | * node daemon.js cognicity-reports-config.js start
40 | * node daemon.js cognicity-reports-config.js status
41 | * node daemon.js cognicity-reports-config.js stop
42 | */
43 |
44 | // Node dependencies
45 | var path = require('path');
46 |
47 | // Modules
48 | // ntwitter twitter interface module
49 | var twitter = require('ntwitter');
50 | // Postgres interface module
51 | var pg = require('pg');
52 | // Winston logger module
53 | var logger = require('winston');
54 | // Gnip PowerTrack interface module
55 | var Gnip = require('gnip');
56 | // CognicityReportsPowertrack interface module
57 | var CognicityReportsPowertrack = require('./CognicityReportsPowertrack.js');
58 |
59 | // Verify expected arguments
60 | if (process.argv[2]) {
61 | var config = require( __dirname + path.sep + process.argv[2] );
62 | } else {
63 | throw new Error('No config file. Usage: node app.js config.js');
64 | }
65 |
66 | // Logging configuration
67 | var logPath = ( config.logger.logDirectory ? config.logger.logDirectory : __dirname );
68 | logPath += path.sep;
69 | logPath += config.logger.filename + ".log";
70 |
71 | logger
72 | // Configure custom File transport to write plain text messages
73 | .add(logger.transports.File, {
74 | filename: logPath, // Write to projectname.log
75 | json: false, // Write in plain text, not JSON
76 | maxsize: config.logger.maxFileSize, // Max size of each file
77 | maxFiles: config.logger.maxFiles, // Max number of files
78 | level: config.logger.level // Level of log messages
79 | })
80 | // Console transport is no use to us when running as a daemon
81 | .remove(logger.transports.Console);
82 |
83 | // FIXME This is a workaround for https://github.com/flatiron/winston/issues/228
84 | // If we exit immediately winston does not get a chance to write the last log message.
85 | // So we wait a short time before exiting.
86 | function exitWithStatus(exitStatus) {
87 | logger.info( "Exiting with status " + exitStatus );
88 | setTimeout( function() {
89 | process.exit(exitStatus);
90 | }, 500 );
91 | }
92 |
93 | logger.info("Application starting...");
94 |
95 | // Verify DB connection is up
96 | pg.connect(config.pg.conString, function(err, client, done){
97 | if (err){
98 | logger.error("DB Connection error: " + err);
99 | logger.error("Fatal error: Application shutting down");
100 | done();
101 | exitWithStatus(1);
102 | } else {
103 | logger.info("DB connection successful");
104 | }
105 | });
106 |
107 | // Configure new instance of the ntwitter interface
108 | var twit = new twitter({
109 | consumer_key: config.twitter.consumer_key,
110 | consumer_secret: config.twitter.consumer_secret,
111 | access_token_key: config.twitter.access_token_key,
112 | access_token_secret: config.twitter.access_token_secret
113 | });
114 |
115 | // Verify that the twitter connection was successful, fail if not
116 | twit.verifyCredentials(function (err, data) {
117 | if (err) {
118 | logger.error("twit.verifyCredentials: Error verifying credentials: " + err);
119 | logger.error("Fatal error: Application shutting down");
120 | exitWithStatus(1);
121 | } else {
122 | logger.info("twit.verifyCredentials: Twitter credentials succesfully verified");
123 | }
124 | });
125 |
126 | // Construct new instance of the cognicity module,
127 | // passing in the configuration and pre-configured instances
128 | // of other modules
129 | var server = new CognicityReportsPowertrack(
130 | config,
131 | twit,
132 | pg,
133 | logger,
134 | Gnip
135 | );
136 |
137 | // Handle postgres idle connection error (generated by RDS failover among other possible causes)
138 | pg.on('error', function(err) {
139 | logger.error('Postgres connection error: ' + err);
140 |
141 | logger.info('Enabling caching mode and attempting to reconnect at intervals');
142 | server.enableCacheMode();
143 |
144 | var reconnectionAttempts = 0;
145 | var reconnectionFunction = function() {
146 | // Try and reconnect
147 | pg.connect(config.pg.conString, function(err, client, done){
148 | if (err) {
149 | reconnectionAttempts++;
150 | if (reconnectionAttempts >= config.pg.reconnectionAttempts) {
151 | // We have tried the maximum number of times, tweet admin and exit in failure state
152 | logger.error( 'Postgres reconnection failed' );
153 | logger.error( 'Maximum reconnection attempts reached, exiting' );
154 | server.tweetAdmin( 'Postgres connection cannot be re-established, shutting down', function() {
155 | exitWithStatus(1);
156 | });
157 | } else {
158 | // If we failed, try and reconnect again after a delay
159 | logger.error( 'Postgres reconnection failed, queuing next attempt for ' + config.pg.reconnectionDelay + 'ms' );
160 | setTimeout( reconnectionFunction, config.pg.reconnectionDelay );
161 | }
162 | } else {
163 | // If we succeeded, disable harvester caching mode
164 | logger.info( 'Postgres reconnection succeeded, re-enabling real time processing' );
165 | server.disableCacheMode();
166 | }
167 | });
168 | };
169 |
170 | reconnectionFunction();
171 | });
172 |
173 | // Catch unhandled exceptions, log, and exit with error status
174 | process.on('uncaughtException', function (err) {
175 | logger.error('uncaughtException: ' + err.message + ", " + err.stack);
176 | logger.error("Fatal error: Application shutting down");
177 | server.tweetAdmin( 'Uncaught exception, shutting down', function() {
178 | exitWithStatus(1);
179 | });
180 | });
181 |
182 | // Catch kill and interrupt signals and log a clean exit status
183 | process.on('SIGTERM', function() {
184 | logger.info('SIGTERM: Application shutting down');
185 | exitWithStatus(0);
186 | });
187 | process.on('SIGINT', function() {
188 | logger.info('SIGINT: Application shutting down');
189 | exitWithStatus(0);
190 | });
191 |
192 | // Verify that messages to tweet are acceptable length
193 | if ( !server.areTweetMessageLengthsOk() ) {
194 | logger.error( "Tweet message lengths not ok, shutting down" );
195 | exitWithStatus(1);
196 | }
197 |
198 | // Start up the twitter feed - connect the Gnip stream
199 | if ( config.gnip.stream ) server.connectStream();
200 |
201 |
202 |