2014-02-19 32 views
0

我寫了一個很好的小錯誤報告中間件,它位於所有GET和POST處理(在app.use(app.router);之後)。見下文。錯誤處理ExpressJS中的中間件以產生多個child_process

這個偉大的工程進行簡單快速GET和POST是轉到PostGIS的數據庫等

不過我有一個設計創建一堆目錄,大量的文件,然後產卵1一個POST請求 - > 8個child_processes任務

childProcess.execFile(job.config.FMEPath, ["PARAMETER_FILE", job.fmeParamFile], { cwd: job.root }, 

所有這些設置不會花費太多時間(不到一秒鐘,它是所有異步(我用的異步庫在一個點測序5個步驟(見下文)

我的問題是錯誤處理。現在我重新在創建所有文件並執行所有步驟之前立即轉發響應。這意味着下一個(err)沒有按預期工作。報告錯誤的好範例是什麼?我正在使用WINSTON來記錄錯誤[logger.log()],但是應該只記錄服務器上的錯誤,還是應該將它報告給原始請求。這裏是目前的職位要求(請記住,我會保持休息,並且REQ和下一個對象有相當長一段時間才能夠旁邊致電(ERR)。

exports.build = function (req, res, next) { 
    var config = global.app.settings.config; 
    var jobBatch = groupJobs(req.body.FrameList); 
    var ticket = tools.newGuid("", true); 
    var fileCount = req.body.FrameList.length * nitfMultiplier; 
    var ts = timespan.fromSeconds(fileCount/config.TileRate); 
    var estimate = ts.hours + ":" + tools.pad(ts.minutes, 2) + ":" + tools.pad(ts.seconds, 2); 
    res.set({ 'Content-Type': 'application/json; charset=utf-8' }); 
    res.send({ ticket: ticket, maxTiles: fileCount, timeEstimate: estimate, tileRate: config.TileRate, wwwURL: config.WWWUrl }); 
    jobBatchRoot(req, res, jobBatch, config, ticket, next); 
}; 

jobBatchRoot()(我然後將熄滅,做了很多處理的,我並沒有包括所有的代碼。

exports.bugs = function (err, req, res, next) { 
    global.app.settings.stats.errors += 1; 
    if (err.code == undefined) { 
     err.code = 500; 
     err.message = "Server Error"; 
    } 
    res.status(err.code); 
    logger.log('error', '%s url: %s status: %d \n', req.method, req.url, err.code, { query: req.query, body: req.body, message: err.message, stack: err.stack }); 
    var desc = req.method + " " + req.url; 
    var body = util.format("%j", req.body); 
    var query = util.format("%j", req.query); 
    var stack = err.stack.split('\n'); 
    res.format({ 
     text: function() { 
      res.send(util.format("%j", { title: err.message, code: err.code, desc: desc, query: query, message: err.message, stack: err.stack, body: body})); 
     }, 

     html: function() { 
      query = tools.pretty(req.query); 
      res.render('error', { title: err.message, code: err.code, desc: desc, query: query, message: err.message, stack: stack, body: body }); 
     }, 

     json: function() { 
      res.send({ title: err.message, code: err.code, desc: desc, query: query, message: err.message, stack: err.stack, body: body }); 
     } 
    }); 

}; 

回答

0

我去了,並重新考慮了這一點。我創建了一個module.exports = function(..){...}

然後添加了很多狀態和方法來創建一個類。它包含作業定義。所以現在我創建頂級目錄,返回一個響應,併產生子作業。他們都在明確的迴應後運行異步。但是他們不應該得到錯誤,如果他們這樣做了,那麼我使用WINSTON將他們登錄到服務器中,並且在所有構建完成時還向用戶返回工作完成的信息。

0

也許我應該再保本(也許面向對象),反正我想我會在這裏發佈完整的模塊我所需要的只是關於結構,最佳實踐的一些提示。

var util = require('util'); 
var query = require("pg-query"); 
var timespan = require('timespan'); 
var _ = require('lodash'); 
var path = require('path'); 
var fs = require('fs'); 
var query = require("pg-query"); 
var async = require("async"); 
var childProcess = require("child_process"); 
var tools = require("../tools/tools"); 
var nitfMultiplier = 99; 
var manifestVersionID = 5; 

exports.setup = function (app) { 
}; 

exports.estimate = function (req, res, next) { 
    var config = global.app.settings.config; 
    var fileCount = req.body.FrameList.length * nitfMultiplier; 
    var ts = timespan.fromSeconds(fileCount/config.TileRate); 
    var estimate = ts.hours + ":" + tools.pad(ts.minutes, 2) + ":" + tools.pad(ts.seconds, 2); 
    res.set({ 'Content-Type': 'application/json; charset=utf-8' }); 
    res.send({ ticket: "Estimate", maxTiles: fileCount, timeEstimate: estimate, tileRate: config.TileRate, wwwURL: config.WWWUrl }); 
}; 

exports.build = function (req, res, next) { 
    var config = global.app.settings.config; 
    var jobBatch = groupJobs(req.body.FrameList); 
    var ticket = tools.newGuid("", true); 
    var fileCount = req.body.FrameList.length * nitfMultiplier; 
    var ts = timespan.fromSeconds(fileCount/config.TileRate); 
    var estimate = ts.hours + ":" + tools.pad(ts.minutes, 2) + ":" + tools.pad(ts.seconds, 2); 
    res.set({ 'Content-Type': 'application/json; charset=utf-8' }); 
    res.send({ ticket: ticket, maxTiles: fileCount, timeEstimate: estimate, tileRate: config.TileRate, wwwURL: config.WWWUrl }); 
    jobBatchRoot(req, res, jobBatch, config, ticket, next); 
}; 

function groupJobs(list) { 
    var jobBatch = {}; 
    _.forEach(list, function (obj) { 
     if (jobBatch[obj.type] == undefined) { 
      jobBatch[obj.type] = []; 
     } 
     jobBatch[obj.type].push(obj); 
    }); 
    return jobBatch; 
}; 

function jobBatchRoot(req, res, jobBatch, config, ticket, next) { 
    var batchRoot = path.join(config.JobsPath, ticket); 
    fs.mkdir(batchRoot, function (err) { 
     if (err) return next(err); 
     var mapInfoFile = path.join(batchRoot, "MapInfo.json"); 
     var mapInfo = { 
      Date: (new Date()).toISOString(), 
      Version: manifestVersionID, 
      Zoom: req.body.Zoom, 
      CenterLat: req.body.CenterLat, 
      CenterLon: req.body.CenterLon 
     }; 
     fs.writeFile(mapInfoFile, tools.pretty(mapInfo), function (err) { 
      if (err) return next(err); 
      spawnJobs(req, res, batchRoot, mapInfo, config, ticket, jobBatch, next); 
     }); 
    }); 
}; 

function spawnJobs(req, res, root, mapInfo, config, ticket, jobBatch, next) { 
    _.forEach(jobBatch, function (files, key) { 
     var job = { 
      req: req, 
      res: res, 
      type: key, 
      files: files, 
      batchRoot: root, 
      mapInfo: mapInfo, 
      config: config, 
      ticket: ticket, 
      missingFiles: [], 
      run: true, 
      next: next 
     }; 
     setup(job); 
    }); 
}; 

function setup(job) { 
    job.root = path.join(job.batchRoot, job.type); 
    job.fmeParamFile = path.join(job.root, "fmeParameters.txt"); 
    job.fmeWorkSpace = path.join(job.config.LibrarianPath, "TileBuilder.fmw"); 
    job.fmeLogFile = path.join(job.root, "jobLog.log"); 
    job.errorLog = path.join(job.root, "errorLog.log"); 
    job.jobFile = path.join(job.root, "jobFile.json"); 
    job.manifestFile = path.join(job.root, "manifest.json"); 
    async.series({ 
     one: function (callback) { 
      maxZoom(job, callback); 
     }, 
     two: function (callback) { 
      fs.mkdir(job.root, function (err) { 
       if (err) return job.next(err); 
       callback(null, "Job Root Created"); 
      }); 
     }, 
     three: function (callback) { 
      makeParamFile(job, callback); 
     }, 
     four: function (callback) { 
      delete job.req; 
      delete job.res; 
      fs.writeFile(job.jobFile, tools.pretty(job), function (err) { 
       if (err) return job.next(err); 
       callback(null, "Wrote Job File"); 
      }); 
     }, 
     five: function (callback) { 
      runJob(job, callback); 
     }, 
     six: function (callback) { 
      tileList(job, callback); 
     }, 
     seven: function (callback) { 
      finish(job, callback); 
     }, 
    }, 
    function (err, results) { 
     if (err) return job.next(err); 
     console.log(tools.pretty(results)); 
    }); 
} 

function maxZoom(job, callback) { 
    var qString = util.format('SELECT type, "maxZoom" FROM portal.m_type WHERE type=\'%s\'', job.type); 
    query(qString, function (err, rows, result) { 
     if (err) { 
      var err = new Error(queryName); 
      err.message = err.message + " - " + qString; 
      err.code = 400; 
      return job.next(err); 
     } 
     job.maxZoom = rows[0].maxZoom - 1; // kludge for 2x1 root layer in leaflet 
     return callback(null, "Got MaxZoom"); 
    }); 
} 

function makeParamFile(job, callback) { 
    var text = util.format("%s\n", job.fmeWorkSpace); 
    text += util.format("--OutputDir %s\n", job.root); 
    text += util.format("--LogFile %s\n", job.fmeLogFile); 
    var source = ""; 
    _.forEach(job.files, function (file) { 
     var path = ('development' == process.env.NODE_ENV) ? file.path.replace(job.config.SourceRootRaw, job.config.SourceRoot) : file.path; 
     if (fs.existsSync(path)) { 
      source += wrap(path, '\\"') + " "; 
     } 
     else { 
      job.missingFiles.push(path); 
     } 
    }); 
    source = wrap(wrap(source, '\\"'), '"'); 
    text += "--Sources " + source; 
    if (job.missingFiles.length == job.files.length) job.run = false; 
    fs.writeFile(job.fmeParamFile, text, function (err) { 
     if (err) return job.next(err); 
     return callback(null, "Wrote Paramaters File"); 
    }) 
}; 

function wrap(content, edge) { 
    return edge+content+edge; 
    } 

function runJob(job, callback) { 
    if (!job.run) return callback(null, "Skipped JOB, no files"); 
    childProcess.execFile(job.config.FMEPath, ["PARAMETER_FILE", job.fmeParamFile], { cwd: job.root }, 
     function (err, stdout, stderr) { 
      if (err) return job.next(err); 
      job.stdout = stdout; 
      job.stderr = stderr; 
      var bar = "\n--------------------------------------------------------------------------------------------------------\n"; 
      var results = util.format("%s STDOUT: \n %s%s STDERR: \n %s", bar, job.stdout, bar, job.stderr); 
      fs.appendFile(job.fmeLogFile, results, function (err) { 
       return callback(err, "FME JOB " + job.type + " run completed"); 
      }); 
     }); 
} 

function tileList(job, callback) { 
    var tiles = []; 
    var byteCount = 0; 
    fs.readdir(job.root, function (err, files) { 
     if (err) { 
      logger.log('error', 'tileList directory read: %s \n', job.root, { message: err.message, stack: err.stack }); 
      return job.next(err); 
     } 
     async.each(files, function (file, done) { 
      var fileName = file.split("."); 
      fs.lstat(job.root + "\\" + file, function (err, stats) { 
       if (!err && stats.isFile() && (fileName[1] == "png")) { 
        tiles.push({ id: fileName[0], size: stats.size }); 
        byteCount += stats.size; 
       }; 
       done(null); 
      }); 
     }, function (err) { 
     job.tileList = tiles; 
     job.byteCount = byteCount; 
     return callback(null, "got tile list");} 
     ); 
    }); 
} 

function finish(job, callback) { 
    var manifest = { 
     Date: (new Date()).toISOString(), 
     Version: manifestVersionID, 
     MaxZoom: job.maxZoom, 
     Class: "OVERLAY", 
     FileCount: job.tileList.length, 
     Size: job.byteCount/(1024 * 1024), // Mbytes 
     files: job.tileList 
    }; 
    fs.writeFile(job.manifestFile, tools.pretty(manifest), function (err) { 
     if (err) { 
      logger.log('error', 'manifest write: %s \n', job.manifestFile, { message: err.message, stack: err.stack }); 
      return job.next(err); 
     } 
     return callback(null, "JOB " + job.type + " completed"); 
    }); 
}