docs: function (app, ctx, options, err) {
      if (!app.isPrevent(err)) {
          ctx.status = typeof err.status === 'number' ? err.status : (options.error_code || 500);
          // accepted types
          switch (ctx.accepts('json', 'html', 'text')) {
              case 'json':
                  ctx.type = 'application/json';
                  ctx.res.end(`{"status": 0,"${options.error_no_key || 'code'}": ${ctx.status},"${options.error_msg_key || 'message'}":"${err.message || ''}"}`);
                  break;
              case 'html':
                  ctx.type = 'text/html';
                  let body = `<!DOCTYPE html><html><head><title>Error - ${ctx.status}</title><meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0">
              <style>body {padding: 50px 80px;font: 14px 'Microsoft YaHei','微软雅黑',Helvetica,Sans-serif;}h1, h2 {margin: 0;padding: 10px 0;}h1 {font-size: 2em;}h2 {font-size: 1.2em;font-weight: 200;color: #aaa;}pre {font-size: .8em;}</style>
              </head><body><div id="error"><h1>Error</h1><p>Oops! Your visit is rejected!</p>`;
                  // if (app.app_debug || err.expose) {
                  if (app.app_debug) {
                      body += `<h2>Message:</h2><pre><code>${err.message || ''}</code></pre><h2>Stack:</h2><pre><code>${err.stack || ''}</code></pre>`;
                  }
                  body += '</div></body></html>';
                  ctx.res.end(body);
                  break;
              case 'text':
              default:
                  ctx.type = 'text/plain';
                  ctx.res.end(`Error: ${err.message || ''}`);
                  break;
          }
          app.emit('error', err, ctx);
          return app.prevent();
      }
      return null;
  }