Heroku应用程序在Foreman中运行,但在部署时行为不正确

时间:2014-01-13 23:55:56

标签: python debugging heroku flask

我在Heroku上部署了一个Flask应用程序,在Foreman中本地运行得很好,但在部署时会出现问题。

该应用尝试使用GET方法中提供的符号构建股票图表。如果给定的股票代码不存在,我有一个IOError异常集。通常,任何无效的股票代码都将被忽略,任何有效的股票代码都将呈现其图表。

问题在于,虽然这在本地运行得非常好,但是一旦部署了应用程序,every single symbol is considered invalid and never gets rendered。例如,AAPL在本地呈现图表,但在已部署的版本中返回“未找到符号”。我尝试删除异常块并让应用程序崩溃,但我看到的只是500错误页面。

我还尝试添加一些打印输出日志来检查事情何时变得混乱。如果你有兴趣,这是Foreman输出:

18:15:37 web.1  | testing for length of symbol array...
18:15:37 web.1  | building plots...
18:15:37 web.1  | building data...
18:15:38 web.1  | generating data...
18:15:38 web.1  | building RSI plot...
18:15:38 web.1  | building main plot...
18:15:38 web.1  | building MACD plot...
18:15:38 web.1  | building grid...
18:15:38 web.1  | building snippet...
18:15:38 web.1  | creating signals...
18:15:38 web.1  | AAPL SMA signal says:
18:15:38 web.1  | WAIT
18:15:38 web.1  | AAPL Bollinger breakout signal says:
18:15:38 web.1  | WAIT
18:15:38 web.1  | AAPL RSI signal says:
18:15:38 web.1  | WAIT
18:15:38 web.1  | returning snippet...
18:15:38 web.1  | rendering template...

这是Heroku的日志:

2014-01-13T23:38:11.722125+00:00 app[web.1]: testing for length of symbol array...
2014-01-13T23:38:11.722125+00:00 app[web.1]: building plots...
2014-01-13T23:38:11.722125+00:00 app[web.1]: building data...
2014-01-13T23:38:12.226672+00:00 app[web.1]: generating data...
2014-01-13T23:38:12.228213+00:00 app[web.1]: building RSI plot...
2014-01-13T23:38:12.247723+00:00 app[web.1]: building grid...
2014-01-13T23:38:12.248022+00:00 app[web.1]: building snippet...
2014-01-13T23:38:12.236035+00:00 app[web.1]: building main plot...
2014-01-13T23:38:12.243688+00:00 app[web.1]: building MACD plot...
2014-01-13T23:38:12.477185+00:00 app[web.1]: rendering template...
2014-01-13T23:38:12.477185+00:00 app[web.1]: symbol AAPL not found

对于这些日志,真的奇怪的是“未找到符号AAPL”应该在“渲染模板”之前打印,而不是之后,无论有多少符号发现或未找到。另外,在构建JS片段之后创建了两个图。所以事情正在发生。我不知道为什么,除非那只是因为他们的时间戳......但是这会改变什么呢?

我注意到在another question中,如果应用程序崩溃,日志会输出回溯。查看堆栈跟踪将非常有用,因此我可以看到Foreman的输出和Heroku日志之间的区别。但是,我没有在两者中找到任何追溯,所以我不知道究竟是什么打破了。 (这可能也是因为我正在使用的一个名为Bokeh的模块出错了,在这种情况下,由于Heroku安装了该模块本身,所以我无法做任何事情,无论如何它都适用于我的机器。)< / p>

当我部署它时,为什么它不能正常工作,我感到很神秘,但在其他地方工作。如果你想看到它的实际效果,你可以try to pull up Apple's stock page,但是你会得到“找不到符号”的错误。


这是我的所有代码。

routes.py:

from flask import Flask, render_template, request
import plots

app = Flask(__name__)


# Define and add a home page.

@app.route('/')  # The base URL for the home page.
def resume():
    return render_template('resume.html')


@app.route('/echo/<message>')
def echo(message):
    # Echos the message passed to it.
    return "%s" % message


@app.route('/stocks')
def lookup():

    s = request.args.get('symbol')

    print "testing for length of symbol array..."

    try:
        len(s)
    except TypeError:
        return render_template('stocks.html',
                               error='Please request a stock symbol.',
                               s_length=0,
                               sd_length=0)

    if len(s) == 0:
        return render_template('stocks.html',
                               error='Please request a stock symbol.',
                               s_length=0,
                               sd_length=0)

    symbols_list = s.split(',')

    snippet_dict = {}

    print "building plots..."
    for i in symbols_list:
        try:
            snippet_dict[i] = plots.build_plot(i.upper())
        except IOError:
            print "symbol %s not found" % i
            continue

    print "rendering template..."

    if len(snippet_dict) == 0:
        return render_template('stocks.html',
                               error='No valid symbols found.',
                               s_length=0,
                               sd_length=0)

    return render_template('stocks.html',
                           snippet_dict=snippet_dict,
                           sd_length=len(snippet_dict))

# Run this thing!
if __name__ == '__main__':
    app.run(debug=True)

plots.py:

from bokeh.plotting import *
from bokeh.objects import Range1d

import pandas
import pandas.io.data as web
from pyta import *
import numpy as np

# Creates a set of stock charts in Bokeh.

#Trying to add signals to the indicators.

def sma_signal(sma50, sma200):
    output_array = []
    for i in range(len(sma50)):
        if i == 0:
            output_array.append('BEGIN')
            continue
        if pandas.isnull(sma50[i]):
            output_array.append('WAIT')
        elif sma50[i] > sma200[i]:
            if sma50[i-1] < sma200[i-1]:
                output_array.append('BUY')
            else:
                output_array.append('WAIT')
        elif sma50[i] < sma200[i]:
            if sma50[i-1] > sma200[i-1]:
                output_array.append('SELL')
            else:
                output_array.append('WAIT')
        else:
            output_array.append('WAIT')
    return output_array


def boll_signal(price, upperband, lowerband):
    output_array = []
    for i in range(len(upperband)):
        if price[i] > upperband[i]:
            output_array.append('BUY')
        elif price[i] < lowerband[i]:
            output_array.append('SELL')
        else:
            output_array.append('WAIT')
    return output_array


def rsi_signal(rsi):
    output_array = []
    for i in range(len(rsi)):
        if rsi[i] > 70:
            output_array.append('OVERBOUGHT')
        elif rsi[i] < 30:
            output_array.append('OVERSOLD')
        else:
            output_array.append('WAIT')
    return output_array

# Create a plot for each symbol.


def build_data(symbol):

    data = web.DataReader(symbol, 'google')
    close = data['Close']

    sma50 = sma(close, 50)
    data['SMA50'] = sma50

    sma200 = sma(close, 200)
    data['SMA200'] = sma200

    upperband = bollinger_upper(close, sma50, 50)
    lowerband = bollinger_lower(close, sma50, 50)
    data['Bollinger (upper)'] = upperband
    data['Bollinger (lower)'] = lowerband

    rsi50 = rsi(close)
    data['RSI50'] = rsi50

    macd = macd_line(close)
    signal = macd_signal(macd)
    hist = macd_hist(macd, signal)

    data['MACD Line'] = macd
    data['MACD Signal'] = signal
    data['MACD Histogram'] = hist

    return data



def build_plot(symbol):

    main_plot_title = symbol

    print "building data..."
    data = build_data(symbol)

    # Generate data.

    print "generating data..."
    file_name = '%s.html' % symbol
    output_file(file_name,
                title='How are my stocks doing today?')

    close = data['Close']
    dates = data.index

    # Define plot constants.

    plot_width = 1000

    # Perform TA on stock data.
    # TODO: Make the buy/sell signals actually do something.

    # Define SMA 50/200.
    sma50 = data['SMA50']
    sma200 = data['SMA200']

    # Define Bollinger Bands.
    upperband = data['Bollinger (upper)']
    lowerband = data['Bollinger (lower)']

    # Define RSI.
    rsi50 = data['RSI50']  # TODO: Figure out a way to translate "crosses under/above line" to a sell signal.


    # Define MACD Line, Signal, and Histogram.
    macd = data['MACD Line']
    signal = data['MACD Signal']  # TODO: Actually use these. Figure out subplots!
    hist = data['MACD Histogram']


    # Finished with the data? Then it's time to plot!

    x = data.index
    y = close

    # Plot RSI (remember, it goes on top)

    # Predefine plot for axis buggery.

    print "building RSI plot..."

    rsi_plot = line(x, rsi50,
                    color='#000000',
                    x_axis_type=None)

    # Define RSI axis boundaries.
    x_range = Range1d(start=x[0], end=x[-1])
    xbounds = [x[0], x[-1]]

    curplot().y_range = Range1d(start=0, end=100)
    curplot().x_range = x_range

    yaxis().bounds = [0, 100]
    xaxis().bounds = xbounds

    hold()

    rsi_plot = line(x, rsi50,
                    color='#000000',
                    x_axis_type=None)

    line(x, (np.ones(len(rsi50)) * 30),
         color='#4daf4a')

    line(x, (np.ones(len(rsi50)) * 70),
         color='#e41a1c')

    # Miscellaneous plot attributes.

    rsi_plot.title = main_plot_title
    rsi_plot.height = 200
    rsi_plot.width = plot_width
    rsi_plot.min_border_bottom = 10
    grid().grid_line_alpha = 0.4
    yaxis().axis_label = 'RSI'

    # Remove hold for the main plot.
    hold()


    print "building main plot..."
    # Plot raw stock data (main plot).
    main_plot = line(x, y,
                     color='#1B9E77',
                     legend='Price at Close',
                     x_axis_type=None,
                     title='')

    # Hold for the overlays.
    hold()

    # Plot overlays.

    # SMA 50:
    line(x, sma50,
         color='#D95F02',
         legend='50-day SMA')

    # SMA 200:
    line(x, sma200,
         color='#e7298a',
         legend='200-day SMA')

    # Bollinger shading glyph:
    bandprice = stackify(upperband, lowerband)  # Reverse the upper band data and append it to the lower band data.
    banddates = stackify(dates, dates)  # Do the same for the dates.
    patch(pandas.to_datetime(banddates), bandprice,
          color='#7570B3',
          fill_alpha=0.2)

    # Remove hold, allow for more plots to be added.
    hold()

    # Miscellaneous plot attributes.
    main_plot.height = 600
    main_plot.width = plot_width
    yaxis().axis_label = 'Price (USD)'
    grid().grid_line_alpha = 0.4

    xaxis().bounds = xbounds
    curplot().x_range = x_range

    # Plot MACD.

    print "building MACD plot..."
    macd_plot = line(x, macd,
         color='#D95F02',
         title='',
         x_axis_type='datetime')

    hold()

    line(x, signal,
         color='#1B9E77')

    hold()

    # Attributes.
    macd_plot.height = 200
    macd_plot.width = plot_width
    yaxis().axis_label = 'MACD'

    macd_plot.min_border_top = 0
    macd_plot.min_border_bottom = 100

    # Make a grid and snippet from this.

    print "building grid..."

    plot_grid = gridplot([[rsi_plot], [main_plot], [macd_plot]])

    print "building snippet..."

    snippet = plot_grid.create_html_snippet(embed_base_url='../static/js/temp/',
                                            embed_save_loc='./static/js/temp')

    # Return signal arrays.

    print "creating signals..."

    sma_signals = sma_signal(sma50, sma200)
    boll_signals = boll_signal(close, upperband, lowerband)
    rsi_signals = rsi_signal(rsi50)

    print '%s SMA signal says:' % symbol
    print sma_signals[-1]
    print '%s Bollinger breakout signal says:' % symbol
    print boll_signals[-1]
    print '%s RSI signal says:' % symbol
    print rsi_signals[-1]

    print "returning snippet..."

    return snippet

令人讨厌的代码:

print "building plots..."
for i in symbols_list:
    try:
        snippet_dict[i] = plots.build_plot(i.upper())
    except IOError:
        print "symbol %s not found" % i
        continue

  

Heroku日志中有什么有趣的东西吗?

这就是我得到的全部。

2014-01-14T01:24:13.000718+00:00 heroku[web.1]: Unidling
2014-01-14T01:24:13.001093+00:00 heroku[web.1]: State changed from down to starting
2014-01-14T01:24:17.900834+00:00 heroku[web.1]: Starting process with command `gunicorn routes:app`
2014-01-14T01:24:18.796042+00:00 app[web.1]: 2014-01-14 01:24:18 [2] [INFO] Starting gunicorn 18.0
2014-01-14T01:24:18.796610+00:00 app[web.1]: 2014-01-14 01:24:18 [2] [INFO] Listening at: http://0.0.0.0:25179 (2)
2014-01-14T01:24:18.796681+00:00 app[web.1]: 2014-01-14 01:24:18 [2] [INFO] Using worker: sync
2014-01-14T01:24:18.802306+00:00 app[web.1]: 2014-01-14 01:24:18 [7] [INFO] Booting worker with pid: 7
2014-01-14T01:24:21.078033+00:00 app[web.1]: testing for length of symbol array...
2014-01-14T01:24:21.078033+00:00 app[web.1]: building plots...
2014-01-14T01:24:21.078033+00:00 app[web.1]: building data...
2014-01-14T01:24:21.385022+00:00 app[web.1]: generating data...
2014-01-14T01:24:21.386059+00:00 app[web.1]: building RSI plot...
2014-01-14T01:24:21.390727+00:00 app[web.1]: building main plot...
2014-01-14T01:24:21.398065+00:00 app[web.1]: building MACD plot...
2014-01-14T01:24:21.398388+00:00 app[web.1]: building grid...
2014-01-14T01:24:21.398541+00:00 app[web.1]: building snippet...
2014-01-14T01:24:21.548095+00:00 app[web.1]: symbol AAPL not found
2014-01-14T01:24:21.548270+00:00 app[web.1]: rendering template...
2014-01-14T01:24:21.593435+00:00 heroku[router]: at=info method=GET path=/stocks?symbol=AAPL host=rpazyaquian.herokuapp.com fwd="69.180.75.108" dyno=web.1 connect=5ms service=518ms status=200 bytes=1635
2014-01-14T01:24:21.774327+00:00 heroku[router]: at=info method=GET path=/static/css/main.css host=rpazyaquian.herokuapp.com fwd="69.180.75.108" dyno=web.1 connect=4ms service=19ms status=200 bytes=1081
2014-01-14T01:24:21.882153+00:00 heroku[router]: at=info method=GET path=/static/css/continuum.css host=rpazyaquian.herokuapp.com fwd="69.180.75.108" dyno=web.1 connect=2ms service=5ms status=200 bytes=370
2014-01-14T01:24:21.954590+00:00 heroku[router]: at=info method=GET path=/static/js/bokeh.js host=rpazyaquian.herokuapp.com fwd="69.180.75.108" dyno=web.1 connect=1ms service=22ms status=200 bytes=489909
2014-01-14T01:24:21.963913+00:00 heroku[router]: at=info method=GET path=/static/css/normalize.css host=rpazyaquian.herokuapp.com fwd="69.180.75.108" dyno=web.1 connect=33ms service=30ms status=200 bytes=7546
2014-01-14T01:24:23.059117+00:00 heroku[router]: at=info method=GET path=/favicon.ico host=rpazyaquian.herokuapp.com fwd="69.180.75.108" dyno=web.1 connect=2ms service=12ms status=404 bytes=233
2014-01-14T01:24:21.773260+00:00 heroku[router]: at=info method=GET path=/static/css/bootstrap-bokeh-2.0.4.css host=rpazyaquian.herokuapp.com fwd="69.180.75.108" dyno=web.1 connect=2ms service=19ms status=200 bytes=106102
2014-01-14T01:24:21.861494+00:00 heroku[router]: at=info method=GET path=/static/css/bokeh.css host=rpazyaquian.herokuapp.com fwd="69.180.75.108" dyno=web.1 connect=3ms service=8ms status=200 bytes=2464
2014-01-14T01:24:19.498609+00:00 heroku[web.1]: State changed from starting to up
2014-01-14T01:24:21.903080+00:00 heroku[router]: at=info method=GET path=/static/css/unsemantic.css host=rpazyaquian.herokuapp.com fwd="69.180.75.108" dyno=web.1 connect=3ms service=10ms status=200 bytes=42798

有趣的是,它实际上需要一段时间才能显示最后两三行,即使它们应该同时发生。


删除异常块分别从Foreman和Heroku获取这些日志:

23:53:45 web.1  | started with pid 8846
23:53:45 web.1  | 2014-01-13 23:53:45 [8846] [INFO] Starting gunicorn 18.0
23:53:45 web.1  | 2014-01-13 23:53:45 [8846] [INFO] Listening at: http://0.0.0.0:5000 (8846)
23:53:45 web.1  | 2014-01-13 23:53:45 [8846] [INFO] Using worker: sync
23:53:45 web.1  | 2014-01-13 23:53:45 [8849] [INFO] Booting worker with pid: 8849
23:53:58 web.1  | testing for length of symbol array...
23:53:58 web.1  | building plots...
23:53:58 web.1  | building data...

2014-01-14T04:58:03.316195+00:00 app[web.1]: testing for length of symbol array...
2014-01-14T04:58:03.316195+00:00 app[web.1]: building plots...
2014-01-14T04:58:03.316195+00:00 app[web.1]: building data...
2014-01-14T04:58:05.360351+00:00 heroku[router]: at=info method=GET path=/stocks?symbol=notasymbol host=rpazyaquian.herokuapp.com fwd="69.180.75.108" dyno=web.1 connect=1ms service=2054ms status=500 bytes=291

我在Foreman上没有得到500错误,但我在日志中做了。我似乎仍然不清楚为什么我得到了500。显然,这是一个IOError,但对于有效的符号,它不会发生......它会发生。

2014-01-14T04:57:05.540709+00:00 app[web.1]: testing for length of symbol array...
2014-01-14T04:57:05.540709+00:00 app[web.1]: building plots...
2014-01-14T04:57:05.540709+00:00 app[web.1]: building data...
2014-01-14T04:57:06.170323+00:00 app[web.1]: generating data...
2014-01-14T04:57:06.183668+00:00 app[web.1]: building grid...
2014-01-14T04:57:06.171197+00:00 app[web.1]: building RSI plot...
2014-01-14T04:57:06.176253+00:00 app[web.1]: building main plot...
2014-01-14T04:57:06.180958+00:00 app[web.1]: building MACD plot...
2014-01-14T04:57:06.183783+00:00 app[web.1]: building snippet...
2014-01-14T04:57:06.338506+00:00 heroku[router]: at=info method=GET path=/stocks?symbol=AAPL host=rpazyaquian.herokuapp.com fwd="69.180.75.108" dyno=web.1 connect=2ms service=809ms status=500 bytes=291

0 个答案:

没有答案